Search code examples
c++reflectionc++17template-meta-programmingsfinae

Macro for detecting non-static non-type members in classes


In C++ Templates - The complete guide, 2nd edition, at page 434, a macro is defined to generate predicates testing the existence of a nontype memeber in a class:

#include <type_traits>

#define DEFINE_HAS_MEMBER(Member)                    \
    template<typename T, typename = void>            \
    struct HasMemberT_##Member : std::false_type {}; \
    template<typename T>                             \
    struct HasMemberT_##Member<T, std::void_t<decltype(&T::Member)>> : std::true_type {};

The the text reads

It would not be difficult to modify the partial specialization to exclude cases wehre &T::Member is not a pointer-to-member type (which amounts to excluding static data members).

So I've played around with static_asserts and compiler errors and remember about types of static and non-static of members of classes:

struct A {
    int begin;
    static int end;
};
static_assert(std::is_same<decltype(&A::begin), decltype(A::begin) A::*>::value, ""); // passes
static_assert(std::is_same<decltype(&A::end), decltype(A::end)*>::value, "");         // passes

And thought that maybe a good modification of the lambda above aimed at detecting non-static members only is to change

: std::true_type

to

: std::is_same<decltype(&T::Member), decltype(T::Member) T::*>

whereas if I wanted to check for static memebers only, I could change it to

: std::is_same<decltype(&T::Member), decltype(T::Member)*>

Is it really this easy? Or am I overlooking something important?


Solution

  • You are overlooking non-static member functions.

    The primary use case of the traits created by DEFINE_HAS_MEMBER is to check for the existence of a particular method. To borrow from the terminology in the question, a range is defined by having begin() and end(). While the trait created by the original macro works, your extra condition does not.

    prog.cc:7:61: error: invalid use of non-static member function 'int A::begin()'
        7 | static_assert(std::is_same<decltype(&A::begin), decltype(A::begin) A::*>::value, "");
          |                                                             ^~~~~
    prog.cc:7:72: error: template argument 2 is invalid
        7 | static_assert(std::is_same<decltype(&A::begin), decltype(A::begin) A::*>::value, "");
          |                                                
    

    The reason in standartese is this:

    [expr.prim.id]

    2 An id-expression that denotes a non-static data member or non-static member function of a class can only be used:

    • as part of a class member access in which the object expression refers to the member's class or a class derived from that class, or
    • to form a pointer to member ([expr.unary.op]), or
    • if that id-expression denotes a non-static data member and it appears in an unevaluated operand. [ Example:
      struct S {
        int m;
      };
      int i = sizeof(S::m);           // OK
      int j = sizeof(S::m + 42);      // OK
      
      — end example ]

    These are the only valid uses of something like T::Member. Non static member functions don't hit any of the bullets, so the construct is ill-formed when used to define the body of HasMemberT_##Member's specialization. No SFINAE, just a hard error.