How to detect whether a struct’s member is a bit-field at compile time in C++?

Given a struct S:

struct S {
  bool a : 1;
  bool b : 1;
};

How to determine that S::a and S::b are bit-fields at compile time?

I was trying to come up a macro like IsBitField(S, a) but had hard time to apply SFINAE with offsetof() or addressof() (which are known to be invalid operations per bit-fields).

Answer

First, for a SFINAE solution, the ellipsis overload trick (with expression SFINAE) still works, we just need to find a construct that is ill-formed when applied to a bitfield while well-formed when applied on a normal member.

For example, you cannot form a pointer-to-member if the member is a bitfield.

So we could write:

template <typename T>
struct is_bit_field_t {
    template <typename U>
    static constexpr bool helper(...) {
        return true;
    }
    template <typename U>
    static constexpr bool helper(decltype(&U::a) arg) {
        return false;
    }
    static constexpr bool value = helper<T>(nullptr); 
};

This will test whether the public member a of U is of bitfield type. Explanation:

  • If T::a is a bitfield, then the expression decltype(&U::a) would be ill-formed, which would then SFINAE out the second overload.

Live demo: https://godbolt.org/z/TTn8YPKW3


With C++20, this could be done with concepts, utilizing the fact that you cannot take the address of a bitfield.

The core implementation would just look like:

template <typename T>
concept A_IsBitField = !requires (T t) {
     { &t.a };
};

Live demo: https://godbolt.org/z/W3jbosY93

If you hate to define a standalone concept, you could just use the requires expression instead (with the SFINAE method you have no choice but to create that struct)


Of course, the above only works for the member named a. If you would like to use it with other members, then you need to rewrite the SFINAE trait or the concept, replacing a with whatever member you want. You could write a macro to help you generate those constructs if you need a lot of them.