evnu
evnu

Reputation: 6690

std::enable_if to conditionally compile a member function

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.

Upvotes: 203

Views: 200051

Answers (9)

Jan Schultke
Jan Schultke

Reputation: 40199

The issue is that both declarations of foo() differ in nothing but the default template arguments, and that doesn't make them separate declarations. Defining the second foo() is just re-defining the first foo().

Here is a summary of options, from most modern to least modern:

Conditionally compile member function with requires clause

T foo() requires some_condition<T> {
    return 10;
}

// Trailing requires clause could also be omitted here because the first 
// function is more constrained, and overload resolution will prefer it.
T foo() requires (!some_condition<T>) {
    return 5;
}

Workaround: select implementation with if constexpr

// This can only be used to select an implementation, not to make foo()
// disappear entirely.
T foo() {
    if constexpr (some_condition<T>) {
        // If this part more complex, you can put it in a private member function.
        return 10;
    }
    else {
        return 5;
    }
}

Proper use of std::enable_if

// note: Unfortunately, we pollute the function signature with additional template
//       parameters.
// note: std::enable_if_t can be used in C++14
// note: The additional U parameter is necessary to make the condition dependent
//       on a template parameter; otherwise the compiler will simply emit an error
//       for some_condition<T>.
template <typename U = T, typename std::enable_if<some_condition<U>, int>::type = 0>
T foo() {
    return 10;
}

template <typename U = T, typename std::enable_if<!some_condition<U>, int>::type = 0>
T foo() {
    return 5;
}

Alternative style for std::enable_if

// do SFINAE in trailing return type
template <typename U = T>
auto foo()
  -> typename std::enable_if<some_condition<U>, T>::type {
    return 10;
}

// do SFINAE in regular return type
template <typename U = T>
typename std::enable_if<!some_condition<U>, T>::type
foo() {
    return 5;
}

Mixins

template <typename T, bool B>
struct mixin;

template <typename T>
struct mixin<T, true> {
    T foo() { return 10; }
};

template <typename T>
struct mixin<T, false> {
    T foo() { return 5; }
};

template <typename T>
struct Y : mixin<T, some_condition<T>> {};

Upvotes: 7

Zhi
Zhi

Reputation: 1

// Try this one:

#include <iostream>
#include <type_traits>

// suppose you want to disable certain member functions based on the tag
struct FooTag;
struct BarTag;

// macro to save some typings in the following
// note that a dummy typename is involved in both the 
// first and second parameters. 
// this should be different than the template parameter of the class (typename T for Widget below)

#define EnableIfFoo(T) \
template <typename Dummy = void, typename = \
          typename std::enable_if<std::is_same<FooTag, T>::value, Dummy>::type>

#define EnableIfBar(T) \
template <typename Dummy = void, typename = \
          typename std::enable_if<std::is_same<BarTag, T>::value, Dummy>::type>

template <typename T>
class Widget {
public:
    // enable this function only if the tag is Bar
    EnableIfFoo(T)
    void print() const { std::cout << "I am a Foo!" << std::endl; }
    
    // enable this function only if the tag is Foo
    EnableIfBar(T)
    void display() const { std::cout << "I am a Bar!" << std::endl; }
};


int main() {
    
    // instantiate a widget with tag Foo
    // only print is enabled; display is not
    Widget<FooTag> fw;
    fw.print();
    //fw.display(); // compile error !!
    
    // instantiate a Widget using tag Bar
    // only display is enabled; print is not
    Widget<BarTag> bw;
    bw.display();
    //bw.print(); // compile error !!
    
    return 0;
}

Upvotes: -1

Aedoro
Aedoro

Reputation: 898

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();
}

Upvotes: -1

jpihl
jpihl

Reputation: 8081

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.

Upvotes: 121

Paul Fultz II
Paul Fultz II

Reputation: 18218

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

Upvotes: 5

user1284631
user1284631

Reputation: 4616

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

Upvotes: 21

Janek Olszak
Janek Olszak

Reputation: 4333

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
}

Upvotes: 12

Gary Powell
Gary Powell

Reputation: 71

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.
};

Upvotes: 7

Johannes Schaub - litb
Johannes Schaub - litb

Reputation: 507393

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.

Upvotes: 146

Related Questions