10

I am trying to get a better understanding of std::enable_if in C++11 and have been trying to write a minimal example: a class A with a member function void foo() that has different implementations based on the type T from the class template.
The below code gives the desired result, but I am not understanding it fully yet. Why does version V2 work, but not V1? Why is the "redundant" type U required?

#include <iostream>
#include <type_traits>

template <typename T>
class A {

    public:

        A(T x) : a_(x) {}

        // Enable this function if T == int
        /* V1 */ // template <           typename std::enable_if<std::is_same<T,int>::value,int>::type = 0>
        /* V2 */ template <typename U=T, typename std::enable_if<std::is_same<U,int>::value,int>::type = 0>
        void foo() { std::cout << "\nINT: " << a_ << "\n"; }

        // Enable this function if T == double
        template <typename U=T, typename std::enable_if<std::is_same<U,double>::value,int>::type = 0>
        void foo() { std::cout << "\nDOUBLE: " << a_ << "\n"; }

    private:

        T a_;

};

int main() {
    A<int> aInt(1); aInt.foo();
    A<double> aDouble(3.14); aDouble.foo();
    return 0;
}

Is there a better way to achieve the desired result, i.e. for having different implementations of a void foo() function based on a class template parameter?

mattu
  • 944
  • 11
  • 24
  • Your example is not an appropriate use of `enable_if`. Simple overloading would solve your case. `enable_if` is mostly useful on a *deduced* template parameter. – Kerrek SB Jan 16 '17 at 00:31
  • Using `std::enable_if` would be suited for separating deduced *floating point* types from, say, *integral* types. Two specific types like this would be better suited for *overloading*. – WhozCraig Jan 16 '17 at 00:40
  • @KerrekSB @WhozCraig How would I overload in this particular case? By using an out-of-class definition `void A::foo() {}` and `void A::foo() {}`? My intention would be that the final code only contains those versions of the function that are required (i.e. no function `A::foo()` if such a function is never called) – mattu Jan 16 '17 at 00:43
  • 1
    As I understand the question is about why and how the redundant `typename U=T` makes the example work, but not without it. Anyone explain this? – A.S.H Jan 16 '17 at 01:04

2 Answers2

4

I know this wont fully answer your question, but it might give you some more ideas and understanding of how you can use std::enable_if.

You could replace your foo member functions with the following and have identical functionality:

template<typename U=T> typename std::enable_if<std::is_same<U,int>::value>::type
foo(){ /* enabled when T is type int */ }

template<typename U=T> typename std::enable_if<std::is_same<U,double>::value>::type
foo(){ /* enabled when T is type double */ }

A while back I gained a pretty good understanding of how enable_if works, but sadly I have forgotten most of its intricacies and just remember the more practical ways to use it.

Jeffrey Cash
  • 1,023
  • 6
  • 12
  • I think you need to also remove the `void` before the function since it is now already included in the `std::enable_if<>`. But thx for this input, helped me to get one step closer to understanding what is going on. Would you say this is a legit way of achieving compile-time polymorphism? – mattu Jan 20 '17 at 19:35
  • @untergam Whoops, nice catch! I'll edit that now. I think it is a legit way of doing thing, I'm just not sure if there's better ways to do it – Jeffrey Cash Jan 20 '17 at 19:37
0

As for the first question: why V1 doesn't work? SFINAE applies only in overload resolution - V1 however causes error at the point where type A is instantiated, well before foo() overload resolution.

I suppose there are lot's of possible implementations - which is the most appropriate depends on an actual case in question. A common approach would be to defer the part of A that's different for different template types to a helper class.

template <typename T>
class A_Helper;

template <>
class A_Helper<int> {
public:
    static void foo( int value ){
        std::cout << "INT: " << value << std::endl;
    }
};

template <>
class A_Helper<double> {
public:
    static void foo( double value ){
        std::cout << "DOUBLE: " << value << std::endl;
    }
};

template <typename T>
class A {
public:

    A( T a ) : a_(a) 
    {}

    void foo(){
        A_Helper<T>::foo(a_);
    }

private:
    T a_;
};

The rest of A can be declared only once in a generic way - only the parts that differ are deferred to a helper. There is a lot of possible variations on that - depending on your requirements...

j_kubik
  • 6,062
  • 1
  • 23
  • 42
  • Thanks @j_kubik for your answer. I see how this solution with templated helper classes would work, but this is exactly the kind of code-bloating that I was trying to avoid in the first place. I would prefer a minimal version with `enable_if` or at least with similarly short syntax. – mattu Jan 17 '17 at 21:51