Reputation: 13075
Given some POD structs similar to this:
struct StandardHeader
{
uint32_t field1;
uint32_t field2;
};
struct TypeA
{
StandardHeader Header;
uint8_t field3;
};
struct TypeB
{
StandardHeader Header;
uint16_t field4;
};
I would like to write a type trait (or something similar that can eventually be used in a static_assert
and std::enable_if
or otherwise disable a templated method) which can detect the existence of the StandardHeader
field as the first member of a standard layout type -- ie. such that reinterpret_cast<StandardHeader*>(&instance)
is safe.
(Essentially, an is-a base type check, except that the types do have to be PODs, which won't be true if I use actual C++ inheritance.)
I was able to write something that used the detection idiom to verify that the type is standard layout and has a Header
member of the correct type:
template<typename, typename = std::void_t<>>
struct HasStandardHeader : std::false_type {};
template<typename T>
struct HasStandardHeader<T,
std::void_t<decltype(std::declval<T>().Header)>>
: std::conditional_t<
std::is_standard_layout_v<T> &&
std::is_same_v<decltype(std::declval<T>().Header), StandardHeader>
, std::true_type, std::false_type> {};
The above partly works, but doesn't verify that the field was the first one.
I tried to add something along the lines of this expression to detect that, but it doesn't work:
static_cast<uint8_t*>(&static_cast<T*>(0)->Header) - static_cast<uint8_t*>(0)) == 0
(Other failed attempts involved calling a constexpr bool
method, but sadly those don't appear to be valid in conditional_t
. Or at least not when using &
or something.)
Ideally I'd prefer something that just detects the field with the correct type is there, without even requiring that it be named Header
. Is that possible? And is there a better way of rewriting the above?
As mentioned before, the end goal is to make this method:
template<typename T>
bool Process(T& data, size_t len);
either disappear or static_assert
if T
isn't a POD type with the correct first member.
Edit: looks like I might have been overcomplicating it a little. Adding this expression makes it work as expected:
offsetof(T, Header) == 0
But now:
Header
?Upvotes: 4
Views: 82
Reputation: 141523
Just check offsetof of the member.
template<typename, typename = std::void_t<>>
struct HasStandardHeader : std::false_type {};
template<typename T>
struct HasStandardHeader<T,
std::void_t<decltype(std::declval<T>().Header)>>
: std::conditional_t<
std::is_standard_layout<T>::value &&
std::is_same<decltype(std::declval<T>().Header), StandardHeader>::value &&
offsetof(T, Header) == 0
, std::true_type, std::false_type> {};
Tested a little on godbolt.
Upvotes: 4