SPMP
SPMP

Reputation: 1223

SFINAE with variadic templates

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

Answers (1)

Barry
Barry

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

Related Questions