Miral
Miral

Reputation: 13075

Detecting first member of POD types for SFINAE

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:

Upvotes: 4

Views: 82

Answers (1)

KamilCuk
KamilCuk

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

Related Questions