2

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));
SPMP
  • 1,181
  • 1
  • 9
  • 24
  • 2
    SFINAE only occurs in the declaration, not the definition, of a template (or specialization). So `tester` isn't SFINAE-enabled. SFINAE is tricky, and this keeps it to a limited spot (so accidental errors will generate errors instead of silent substitution failures) and makes it marginally easier for compiler writers. – Yakk - Adam Nevraumont Sep 17 '15 at 17:21
  • 3
    Substitution failure in the *immediate context* is not an elephant. A class template definition is not in the immediate context. – T.C. Sep 17 '15 at 17:22
  • Hmm.. I need to read up on SFINAE. Is there a way to do what I am trying to do using variadic templates? I do have one approach working using function overloading with vararg functions. – SPMP Sep 17 '15 at 17:28

1 Answers1

8

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);
Barry
  • 286,269
  • 29
  • 621
  • 977
  • Thank you so much for the answer. I tried to see if the solution would still work if I replaced the specialized class as `struct has_member : std::true_type,void_t`. I ended up getting a similar error. Does inheritance not count in the immediate context? – SPMP Sep 17 '15 at 19:33
  • 1
    @user2308211 Correct. Also, you would be inheriting from `void` in the case of success, which isn't allowed anyway. – Barry Sep 17 '15 at 19:41