2

As an exercise in understanding the usage of std::enable_if I tried implementing a wrapper class (struct) to represent a particular type at any given point in time:

#include<type_traits>
#include<typeinfo>
#include<iostream>
using std::enable_if;
using std::is_same;
using std::cout;
using std::endl;

template<typename T>
struct type_wrap{

                 type_wrap(typename enable_if<is_same<int,T>::value,T>::type&& rrT):value(rrT){
                         cout << "The wrapped type is " << typeid(value).name() << endl;
                         cout << "The wrapped value is " << value << endl;
                } 
                 type_wrap(typename enable_if<is_same<float,T>::value,T>::type && rrT):value(rrT){
                         cout << "The wrapped type is " << typeid(value).name() << endl;
                         cout << "The wrapped value is " << value << endl;
                 }

                 T& value;
};

int main(){

        type_wrap<int>(0);
        type_wrap<float>(0.5);
        return(0);
}

The above code does not compile:

so_main.cpp:16:47: error: no type named 'type' in 'std::__1::enable_if<false, int>'; 'enable_if' cannot be used to disable this declaration
                 type_wrap(typename enable_if<is_same<float,T>::value,T>::type && rrT):value(rrT){
                                              ^~~~~~~~~~~~~~~~~~~~~~~
so_main.cpp:26:9: note: in instantiation of template class 'type_wrap<int>' requested here
        type_wrap<int>(0);
        ^
so_main.cpp:12:47: error: no type named 'type' in 'std::__1::enable_if<false, float>'; 'enable_if' cannot be used to disable this declaration
                 type_wrap(typename enable_if<is_same<int,T>::value,T>::type&& rrT):value(rrT){
                                              ^~~~~~~~~~~~~~~~~~~~~
so_main.cpp:27:9: note: in instantiation of template class 'type_wrap<float>' requested here
        type_wrap<float>(0.5);
        ^
2 errors generated.

The code works if I were to remove one of the overloaded constructors, and the corresponding instantiation from main(). But that defeats the whole purpose of this exercise.

Can someone point out the cause for the compilation error?

Dean Seo
  • 5,486
  • 3
  • 30
  • 49
user9196120
  • 381
  • 3
  • 9

2 Answers2

2

SFINAE works on template method(/constructor), here it is your class which is template, you might use the following (even if specialization seems simpler/better in your case):

template<typename T>
struct type_wrap{
    template <typename U,
              std::enable_if_t<std::is_same<int, U>::value
                               && is_same<int, T>::value>* = nullptr>
    type_wrap(U arg) : value(arg){
        // Int case
        std::cout << "The wrapped type is " << typeid(value).name() << std::endl;
        std::cout << "The wrapped value is " << value << std::endl;
    }

    template <typename U,
              std::enable_if_t<std::is_same<float, U>::value
                               && is_same<float, T>::value>* = nullptr>
    type_wrap(U arg) : value(arg){
        // float case
        std::cout << "The wrapped type is " << typeid(value).name() << std::endl;
        std::cout << "The wrapped value is " << value << std::endl;
    }
    T value;
};

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • @user9196120: Typo fixed, Demo added. – Jarod42 Mar 30 '18 at 09:24
  • As you may notice, `type_wrap(0.5)` is invalid with my version as you pass `double` instead of `float`. Whereas specialization create non template methods which allow conversion/promotion. – Jarod42 Mar 30 '18 at 09:26
  • @@Jarod421 Could you provide an explanation. about why you use * for enable_if_t and set it to nullptr? I am curious about the logic involved, and not sure I understand it. – user9196120 Mar 30 '18 at 09:47
  • You might look at [why-should-i-avoid-stdenable-if-in-function-signatures](https://stackoverflow.com/questions/14600201/why-should-i-avoid-stdenable-if-in-function-signatures) which show the different way to use `std::enable_if`. – Jarod42 Mar 30 '18 at 11:02
1

Can someone point out the cause for the compilation error?

Because std::enable_if will make one of your constructors illegal depending on each:

type_wrap<int>(0);
type_wrap<float>(0.5);

int or double will force the other side of std::is_same to have false, in which case std::enable_if has no type:

template<bool B, class T = void>
struct enable_if {}; // int or float will get this on each constructor.

template<class T>
struct enable_if<true, T> { typedef T type; };

Instead, use template specialization as follows:

template<typename T>
struct type_wrap;

template<>
struct type_wrap<float>
{
    type_wrap(float&& rrT) :value(rrT) {
        cout << "The wrapped type is " << typeid(value).name() << endl;
        cout << "The wrapped value is " << value << endl;
    }

    float& value;
};

template<>
struct type_wrap<int>
{
    type_wrap(int&& rrT) :value(rrT) {
        cout << "The wrapped type is " << typeid(value).name() << endl;
        cout << "The wrapped value is " << value << endl;
    }

    int& value;
};

If your compiler supports C++17, if constexpr makes this a lot easier and more straight-forward:

template<typename T>
struct type_wrap
{
    type_wrap(T&& rrT):value(rrT)
    {
        if constexpr (std::is_same<int, T>::value)
        {
            cout << "The wrapped type is " << typeid(value).name() << endl;
            cout << "The wrapped value is " << value << endl;
        }
        else
        {
            cout << "The wrapped type is " << typeid(value).name() << endl;
            cout << "The wrapped value is " << value << endl;
        }
    } 

    T& value;
};
Dean Seo
  • 5,486
  • 3
  • 30
  • 49
  • Thanks for your explanation, but I don't quite get it. My understanding was the purpose of using enable_if() is to enable only one overload based on the particular instantiation. Your observation "int or double will counter the other side of std::is_same to have false," is not clear to me. I get a similar error even if I were to use only one instantiation, i.e., either int or float, in main(). – user9196120 Mar 29 '18 at 07:30
  • @user9196120 The first constructor is illegal if `T` is `float`. The second constructor is illegal if `T` is `int`. There's no SFINAE in this case - your compiler must see two constructors valid in both cases. – Dean Seo Mar 29 '18 at 07:39