170

I am trying to get a simple example to work to understand how to use std::enable_if. After I read this answer, I thought it shouldn't be too hard to come up with a simple example. I want to use std::enable_if to choose between two member-functions and allow only one of them to be used.

Unfortunately, the following doesn't compile with gcc 4.7 and after hours and hours of trying I am asking you guys what my mistake is.

#include <utility>
#include <iostream>

template< class T >
class Y {

    public:
        template < typename = typename std::enable_if< true >::type >
        T foo() {
            return 10;
        }
        template < typename = typename std::enable_if< false >::type >
        T foo() {
            return 10;
        }

};


int main() {
    Y< double > y;

    std::cout << y.foo() << std::endl;
}

gcc reports the following problems:

% LANG=C make CXXFLAGS="-std=c++0x" enable_if
g++ -std=c++0x    enable_if.cpp   -o enable_if
enable_if.cpp:12:65: error: `type' in `struct std::enable_if<false>' does not name a type
enable_if.cpp:13:15: error: `template<class T> template<class> T Y::foo()' cannot be overloaded
enable_if.cpp:9:15: error: with `template<class T> template<class> T Y::foo()'

Why doesn't g++ delete the wrong instantiation for the second member function? According to the standard, std::enable_if< bool, T = void >::type only exists when the boolean template parameter is true. But why doesn't g++ consider this as SFINAE? I think that the overloading error message comes from the problem that g++ doesn't delete the second member function and believes that this should be an overload.

Community
  • 1
  • 1
evnu
  • 5,910
  • 2
  • 23
  • 36
  • 1
    I am not sure, but I think it's the following: enable_if is based on SFINAE (substitution failure is not an error). However, you don't have any substitution here, because no parameter can't be used to determine which overload to use. You should make the "true" und "false" depend on T. (I know you did not want to do it in the simple example, but it's probably too simple now...) – Philipp Aug 07 '11 at 11:13
  • 3
    I thought of that too and tried to use `std::is_same< T, int >::value` and `! std::is_same< T, int >::value` which gives the same result. – evnu Aug 07 '11 at 11:16

7 Answers7

122

SFINAE only works if substitution in argument deduction of a template argument makes the construct ill-formed. There is no such substitution.

I thought of that too and tried to use std::is_same< T, int >::value and ! std::is_same< T, int >::value which gives the same result.

That's because when the class template is instantiated (which happens when you create an object of type Y<int> among other cases), it instantiates all its member declarations (not necessarily their definitions/bodies!). Among them are also its member templates. Note that T is known then, and !std::is_same< T, int >::value yields false. So it will create a class Y<int> which contains

class Y<int> {
    public:
        /* instantiated from
        template < typename = typename std::enable_if< 
          std::is_same< T, int >::value >::type >
        T foo() {
            return 10;
        }
        */

        template < typename = typename std::enable_if< true >::type >
        int foo();

        /* instantiated from

        template < typename = typename std::enable_if< 
          ! std::is_same< T, int >::value >::type >
        T foo() {
            return 10;
        }
        */

        template < typename = typename std::enable_if< false >::type >
        int foo();
};

The std::enable_if<false>::type accesses a non-existing type, so that declaration is ill-formed. And thus your program is invalid.

You need to make the member templates' enable_if depend on a parameter of the member template itself. Then the declarations are valid, because the whole type is still dependent. When you try to call one of them, argument deduction for their template arguments happen and SFINAE happens as expected. See this question and the corresponding answer on how to do that.

Community
  • 1
  • 1
Johannes Schaub - litb
  • 466,055
  • 116
  • 851
  • 1,175
  • 17
    ... Just to clarify, in case it's useful: When an instance of the `Y` template class is instantiated, the compiler will not actually compile the template member functions; however, the compiler WILL perform the substitution of `T` into the member template DECLARATIONS so that these member templates can be instantiated at a later time. This point of failure is not SFINAE, because SFINAE only applies when determining the set of possible functions for *overload resolution*, and instantiating a class is not a case of determining a set of functions for overload resolution. (Or so I think!) – Dan Nissenbaum Mar 29 '13 at 15:57
104

I made this short example which also works.

#include <iostream>
#include <type_traits>

class foo;
class bar;

template<class T>
struct is_bar
{
    template<class Q = T>
    typename std::enable_if<std::is_same<Q, bar>::value, bool>::type check()
    {
        return true;
    }

    template<class Q = T>
    typename std::enable_if<!std::is_same<Q, bar>::value, bool>::type check()
    {
        return false;
    }
};

int main()
{
    is_bar<foo> foo_is_bar;
    is_bar<bar> bar_is_bar;
    if (!foo_is_bar.check() && bar_is_bar.check())
        std::cout << "It works!" << std::endl;

    return 0;
}

Comment if you want me to elaborate. I think the code is more or less self-explanatory, but then again I made it so I might be wrong :)

You can see it in action here.

jpihl
  • 6,841
  • 3
  • 31
  • 42
  • 2
    This doesn't compile on VS2012. `error C4519: default template arguments are only allowed on a class template`. – PythonNut Jun 07 '14 at 13:09
  • 1
    That's unfortunate. I only tested it on with gcc. Maybe this helps: http://stackoverflow.com/a/17543296/660982 – jpihl Jun 10 '14 at 07:52
  • 1
    this is certainly the best answer here and exactly what I was looking for. – Weipeng L Dec 15 '14 at 20:26
  • 3
    Why there is a need to create another template class `Q`, even though it is equal to `T`? – ilya1725 Apr 25 '17 at 18:05
  • 2
    Because you need to template the `test` member function. Both cannot exist at the same time. `Q` just forwards the class template type `T`. You could remove the class template `T` like so: http://cpp.sh/4nxw but that kinda defeats the purpose. – jpihl Apr 26 '17 at 11:25
  • What happens when you omit `= T` and leave it in form `template`? I've tried it and nothing changes (as far as I can see). – PKua Jul 13 '17 at 12:57
  • @jpihl I believe in your last example referenced in the comments line 26 needs to read `if (!check_foo.test() && check_bar.test())` – user1556435 Sep 15 '17 at 14:22
  • @user1556435 if you don't mind me asking, why do you think that? My idea idea is to use the class template as a default for the member function template so that you don't need to specify the type when calling test() :) – jpihl Sep 15 '17 at 18:02
  • ow, sorry I wasn't very clear, I was talking about your later example in the comments, on cpp.sh/4nxw I get syntax errors otherwise: `26:23: error: expected primary-expression before '>' token ...`. Did I misconfigure something? Anyway, your examples have helped me :) – user1556435 Sep 17 '17 at 12:30
  • Ah, I see.. When I re-read your comment is quiet clear what you meant. I must have been tired, thanks for your correction. I'm glad my example was of some value to you. I can't update the original comment, but for what it's worth, here's the updated example with @user1556435's correction: http://cpp.sh/7l4n – jpihl Sep 18 '17 at 08:41
  • 1
    If you're stuck with C++ < 11 (as in my case, Eigen core library development), you also can't use default template arguments (as in VS2012). There's a workaround for that. Leave out the default template argument, and instead add a `Q*` argument to your function specializations. Then create a new function which calls these adapted functions, passing them an additional argument of `Q*` type, e.g. `(Q*)NULL`. See it here: http://cpp.sh/3d6uj (don't forget to check the C++98 compiler option). – Martin Pecka Feb 02 '19 at 02:39
  • This is a nice answer, but I really dislike that the API has to change. Now a user has to consider providing the method invocation with a template parameter; e.g. `bar_is_bar.check()` etc. (which is `false`). – user2023370 Nov 24 '20 at 14:12
  • If what you mean is that it would be better if you couldn't provide the template to the "check()" function I agree. – jpihl Nov 25 '20 at 08:44
16

For those late-comers that are looking for a solution that "just works":

#include <utility>
#include <iostream>

template< typename T >
class Y {

    template< bool cond, typename U >
    using resolvedType  = typename std::enable_if< cond, U >::type; 

    public:
        template< typename U = T > 
        resolvedType< true, U > foo() {
            return 11;
        }
        template< typename U = T >
        resolvedType< false, U > foo() {
            return 12;
        }

};


int main() {
    Y< double > y;

    std::cout << y.foo() << std::endl;
}

Compile with:

g++ -std=gnu++14 test.cpp 

Running gives:

./a.out 
11
user1284631
  • 4,038
  • 30
  • 58
9

From this post:

Default template arguments are not part of the signature of a template

But one can do something like this:

#include <iostream>

struct Foo {
    template < class T,
               class std::enable_if < !std::is_integral<T>::value, int >::type = 0 >
    void f(const T& value)
    {
        std::cout << "Not int" << std::endl;
    }

    template<class T,
             class std::enable_if<std::is_integral<T>::value, int>::type = 0>
    void f(const T& value)
    {
        std::cout << "Int" << std::endl;
    }
};

int main()
{
    Foo foo;
    foo.f(1);
    foo.f(1.1);

    // Output:
    // Int
    // Not int
}
Janek Olszak
  • 3,275
  • 1
  • 23
  • 21
  • It works, but this is basically templating functions, not the class itself... It does not allows dropping one of two identically-prototyped functions neither (when you need to pass over overloading). However, the idea is nice. Could you rewrite the OP example in a working form, please? – user1284631 Sep 30 '14 at 18:14
5

One way to solve this problem, specialization of member functions is to put the specialization into another class, then inherit from that class. You may have to change the order of inheritence to get access to all of the other underlying data but this technique does work.

template< class T, bool condition> struct FooImpl;
template<class T> struct FooImpl<T, true> {
T foo() { return 10; }
};

template<class T> struct FoolImpl<T,false> {
T foo() { return 5; }
};

template< class T >
class Y : public FooImpl<T, boost::is_integer<T> > // whatever your test is goes here.
{
public:
    typedef FooImpl<T, boost::is_integer<T> > inherited;

    // you will need to use "inherited::" if you want to name any of the 
    // members of those inherited classes.
};

The disadvantage of this technique is that if you need to test a lot of different things for different member functions you'll have to make a class for each one, and chain it in the inheritence tree. This is true for accessing common data members.

Ex:

template<class T, bool condition> class Goo;
// repeat pattern above.

template<class T, bool condition>
class Foo<T, true> : public Goo<T, boost::test<T> > {
public:
    typedef Goo<T, boost::test<T> > inherited:
    // etc. etc.
};
leemes
  • 42,229
  • 18
  • 115
  • 172
Gary Powell
  • 51
  • 1
  • 2
4

The boolean needs to depend on the template parameter being deduced. So an easy way to fix is to use a default boolean parameter:

template< class T >
class Y {

    public:
        template < bool EnableBool = true, typename = typename std::enable_if<( std::is_same<T, double>::value && EnableBool )>::type >
        T foo() {
            return 10;
        }

};

However, this won't work if you want to overload the member function. Instead, its best to use TICK_MEMBER_REQUIRES from the Tick library:

template< class T >
class Y {

    public:
        TICK_MEMBER_REQUIRES(std::is_same<T, double>::value)
        T foo() {
            return 10;
        }

        TICK_MEMBER_REQUIRES(!std::is_same<T, double>::value)
        T foo() {
            return 10;
        }

};

You can also implement your own member requires macro like this(just in case you don't want to use another library):

template<long N>
struct requires_enum
{
    enum class type
    {
        none,
        all       
    };
};


#define MEMBER_REQUIRES(...) \
typename requires_enum<__LINE__>::type PrivateRequiresEnum ## __LINE__ = requires_enum<__LINE__>::type::none, \
class=typename std::enable_if<((PrivateRequiresEnum ## __LINE__ == requires_enum<__LINE__>::type::none) && (__VA_ARGS__))>::type
Paul Fultz II
  • 16,112
  • 11
  • 51
  • 58
0

Here is my minimalist example, using a macro. Use double brackets enable_if((...)) when using more complex expressions.

template<bool b, std::enable_if_t<b, int> = 0>
using helper_enable_if = int;

#define enable_if(value) typename = helper_enable_if<value>

struct Test
{
     template<enable_if(false)>
     void run();
}
Aedoro
  • 401
  • 1
  • 4
  • 15