Reputation: 1903
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");
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
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:
&T::foo
is valid, it will check if the member is static or not using std::is_member_pointer
(your version).&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):
T::foo
is valid (a member exists), it returns std::true_type
.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):
&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