就在这个问题因重复而被驳回之前,大多数(如果不是全部)问题已经接受了过时/未定义行为解决方案的答案。
问题:
有没有办法在编译时获取指向成员数据的指针的偏移量,该成员数据:
- 不依赖于未定义的行为(
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
字段偏移量的编译时计算相对容易。下面的解决方案无需nullptr
s,union
s和编译时结构实例化即可工作,并使用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。