Reputation: 1223
I am somewhat new to template programming, so this might be a dumb question. I am trying to use variadic templates to check whether a class has a member (called member
) or not. To do this, I have written the class
has_member
.
#include <iostream>
using namespace std;
class ClassWithMember
{
public:
int member;
};
class ClassWithoutMember
{
};
template <typename T>
class has_member
{
template <typename... C>
class tester: public std::false_type
{
};
template <typename First>
class tester<First>: public std::true_type
{
void tester_fn(decltype(First::member));
};
public:
enum { value = tester<T>::value };
};
template<typename T1>
void my_function(const std::enable_if_t<has_member<T1>::value, T1> &obj)
{
cout<<"Function for classes with member"<<endl;
}
template<typename T1>
void my_function(const std::enable_if_t<!has_member<T1>::value, T1> &obj)
{
cout<<"Function for classes without member"<<endl;
}
int main()
{
ClassWithMember objWithMember;
ClassWithoutMember objWithoutMember;
my_function<ClassWithMember> (objWithMember);
my_function<ClassWithoutMember> (objWithoutMember);
}
I was expecting that by SFINAE, the substitution of the specialized template with classes without the member would fail silently and fall back to the general template. But I get the error:
trial.cpp: In instantiation of ‘class has_member<ClassWithoutMember>::tester<ClassWithoutMember>’:
trial.cpp:28:10: required from ‘class has_member<ClassWithoutMember>’
trial.cpp:38:41: required by substitution of ‘template<class T1> void my_function(std::enable_if_t<(! has_member<T1>::value), T1>&) [with T1 = ClassWithoutMember]’
trial.cpp:49:54: required from here
trial.cpp:24:14: error: ‘member’ is not a member of ‘ClassWithoutMember’
void tester_fn(decltype(First::member));
Upvotes: 2
Views: 1042
Reputation: 303157
SFINAE only applies in the immediate context of the substitution. Substitution failure outside of that is an error. That's the issue you're running into:
has_member<ClassWithoutMember>::value // error
That's because the substitution failure doesn't occur in the declaration of has_member
or tester
, it occurs in the definition. That is too late. You need to push it much earlier. You can use void_t
to push it into the specialization of has_member
:
template <typename... T>
struct make_void { using type = void; };
template <typename... T>
using void_t = typename make_void<T...>::type;
template <typename T, typename = void>
struct has_member : std::false_type { };
template <typename T>
struct has_member<T, void_t<decltype(T::member)>> : std::true_type { };
Now, if there is no T::member
, the substitution failure will occur in the immediate context of the substitution while trying to pick the correct specialization of has_member
. That substitution failure is not an error, that particular specialization would just be discarded and we end up with false_type
as desired.
As a side-note, the way you're using your enable_if_t
prevents template deduction. You should prefer to write it this way:
template <typename T1,
std::enable_if_t<has_member<T1>::value>* = nullptr>
void my_function(const T1& obj) { ... }
template <typename T1,
std::enable_if_t<!has_member<T1>::value>* = nullptr>
void my_function(const T1& obj) { ... }
That would let you just write:
my_function(objWithMember);
my_function(objWithoutMember);
Upvotes: 8