constexpr 偏移量,带有指向成员数据的指针



就在这个问题因重复而被驳回之前,大多数(如果不是全部)问题已经接受了过时/未定义行为解决方案的答案。

问题:

有没有办法在编译时获取指向成员数据的指针的偏移量,该成员数据:

  • 不依赖于未定义的行为(nullptr事物)
  • 适用于最新的 gcc 版本(编译器资源管理器中的gcc trunk)
  • 工作方式与offsetof相同(或类似,就像建议的代码一样)。

我不在乎:

  • 非标准布局类型
  • MSVC 兼容性。(这可能很好,但可选)
  • 具有特定编译器的特殊情况

问题:

对于 GCC 的中继版本,以下技巧(黑客)不起作用:

#include <cstdint>
#include <cstddef>
namespace detail
{
// The union stuff is for CLang to avoid creating warnings
template<typename T, auto MPtr>
struct offsetof_ptr_helper
{
union helper_t { int i = 0;  T value; };
static constexpr helper_t v = {};
static constexpr size_t sz = sizeof
(uint8_t[
(uint8_t *)&(v.value.*MPtr) -
(uint8_t *)&v.value
]);
};
}
template<typename T, auto MPtr>
static constexpr size_t offsetof_ptr()
{
return detail::offsetofptr_helper<T, MPtr>::sz;
}
size_t f()
{
struct X { char c[10]; int a; };
return offsetof_ptr<X, &X::a>();
}

需要明确的是,这是一个使用 C++98 功能的黑客攻击,它的支持被最新的编译器删除也就不足为奇了。它适用于 clang(使用-std=gnu++17标志)和 gcc(包括/高达 g++ 7.3 使用标志-std=c++17)。

对于 GCC 编译器,通过成员指针支持constexpr字段偏移量的编译时计算相对容易。下面的解决方案无需nullptrs,unions和编译时结构实例化即可工作,并使用c++17。也没有诸如可构造性,小尺寸或constexpr兼容类/结构之类的要求

#include <type_traits>
#include <functional>
#include <cstdint>
template<typename> struct struct_member_types;
template<typename T, typename F> struct struct_member_types<F T::*> {
using struct_type = T;
using member_type = F;
};
template<typename T> using struct_type_t = typename struct_member_types<T>::struct_type;
template<typename T> using member_type_t = typename struct_member_types<T>::member_type;
template<auto field> struct constexpr_field_offset {
using T = struct_type_t<decltype(field)>;
using F = member_type_t<decltype(field)>;
static constexpr const char const_holder = 0;
static constexpr const T& const_value = reinterpret_cast<const T&>(const_holder);
static constexpr const F T::* const_field = field;
static constexpr const char* base_addr = &reinterpret_cast<char const&>(const_value);
static constexpr const char* field_addr = &reinterpret_cast<char const&>(const_value.*const_field);
static constexpr const size_t offset = field_addr - base_addr;
};
template<auto field> inline constexpr std::size_t constexpr_field_offset_v = constexpr_field_offset<field>::offset;
struct pod_t {
uint32_t a;
uint16_t b;
uint8_t c;
};
struct __attribute__((__packed__)) tricky_t {
private:
tricky_t() = delete;
tricky_t(const tricky_t&) = delete;
tricky_t(tricky_t&&) = delete;
~tricky_t() = delete;
public:
uint8_t a;
uint32_t b;
};
int main() {
static_assert(
constexpr_field_offset_v<&pod_t::a> == 0,
"Fail"
);
static_assert(
constexpr_field_offset_v<&pod_t::b> == 4,
"Fail"
);
static_assert(
constexpr_field_offset_v<&pod_t::c> == 6,
"Fail"
);
static_assert(
constexpr_field_offset_v<&tricky_t::a> == 0,
"Fail"
);
static_assert(
constexpr_field_offset_v<&tricky_t::b> == 1,
"Fail"
);
return 0;
}

现场演示:https://godbolt.org/z/r163c1KMP

如果你跳到VS中offsetof的定义,它会给你这个:

#define offsetof(s,m) ((::size_t)&reinterpret_cast<char const volatile&>((((s*)0)->m)))

修改了一些以匹配您的问题:

template<typename T, auto MPtr>
static constexpr size_t offsetof_ptr()
{
return ((::size_t) & reinterpret_cast<char const volatile&>((((T*)0)->*MPtr)));
}
size_t f()
{
struct X { char c[10]; int a; };
return offsetof_ptr<X, &X::a>();
}

这在 GCC 和 MSVC 上编译。(由于模板自动参数,它需要 C++17。

最新更新