Viatorus
Viatorus

Reputation: 1903

Type trait: Check if reference member variable is static or not

I would like to check if a member variable of a class is static or not. Using std::is_member_pointer works fine for all types except for reference members.

#include <type_traits>

struct A {
  int foo;
};

struct B : A {};

struct C {
  static int foo;
};

struct D : C {
};

struct E {
  int &foo;
};

struct F {
  static int &foo;
};

static_assert(std::is_member_pointer<decltype(&A::foo)>::value, "No");
static_assert(std::is_member_pointer<decltype(&B::foo)>::value, "No");
static_assert(!std::is_member_pointer<decltype(&C::foo)>::value, "No");
static_assert(!std::is_member_pointer<decltype(&D::foo)>::value, "No");

// Fail to compile:
static_assert(std::is_member_pointer<decltype(&E::foo)>::value, "No");

static_assert(!std::is_member_pointer<decltype(&F::foo)>::value, "No");

Live example.

I understand the error, that a pointer cannot point to a reference member. But how to avoid it and still distinguish if it is a static or non static variable? Any idea on that?

Upvotes: 9

Views: 2643

Answers (1)

Holt
Holt

Reputation: 37641

You could add a fallback in case &E::foo fails using SFINAE (and another one in case E::foo does not exist at all):

template <typename T>
std::is_member_pointer<decltype(&T::foo)> is_member_foo(int);

template <typename T>
decltype(T::foo, std::true_type{}) is_member_foo(long);

template <typename T>
std::false_type is_member_foo(...);

template <typename T>
using IsMemberFoo = decltype(is_member_foo<T>(0));

static_assert(IsMemberFoo<A>{}, "No");
static_assert(IsMemberFoo<B>{}, "No");
static_assert(!IsMemberFoo<C>{}, "No");
static_assert(!IsMemberFoo<D>{}, "No");
static_assert(IsMemberFoo<E>{}, "No");
static_assert(!IsMemberFoo<F>{}, "No");
static_assert(!IsMemberFoo<G>{}, "No"); // struct G { };

What this code does:

  • If &T::foo is valid, it will check if the member is static or not using std::is_member_pointer (your version).
  • If &T::foo is not valid, it falls back to the second overload (here you are sure that foo is not static, or the first overload would have been chosen):
    • If T::foo is valid (a member exists), it returns std::true_type.
    • Otherwize it falls back to the last overload and returns std::false_type.

Also note (thanks to @iammilind) that for private member, T::foo is not valid, so the third overload will be chosen.

Working example on ideone: http://ideone.com/FILHbK

Side notes (extended explanation):

  • When &T::foo is valid, the two first overloads are valid, but the first one is chosen since int is an exact match while long is not.
  • decltype(T::foo, std::true_type{}): T::foo is only here to "let SFINAE" fall back to the third overload if T::foo is not valid, but the resulting type is std::true_type thanks to the comma operator.

If you like, you can also create a generic version (http://ideone.com/lzH2FB):

#define IsMember(MEM) \
template <typename T> \
std::is_member_pointer<decltype(&T::MEM)> is_member_##MEM(int); \
template<typename T> \
decltype(T::MEM, std::true_type{}) is_member_##MEM(long); \
template <typename T> \
std::false_type is_member_##MEM(...); \
template <typename T> \
using IsMember_##MEM = decltype(is_member_##MEM<T>(0));

// Instanciate IsMember_foo
IsMember(foo);

// Use it:
static_assert(IsMember_foo<A>{}, "No");

Also see these two answers if you want to encapsulate everything in a class (without having is_member_ functions):

Upvotes: 2

Related Questions