Search code examples
c++templatesc++14sfinaeenable-if

Why does enable_if_t in template arguments complains about redefinitions?


I have the following case that works using std::enable_if :

template<typename T,
         typename std::enable_if<std::is_same<int, T>::value>::type* = nullptr>
void f() { }

template<typename T,
         typename std::enable_if<std::is_same<double, T>::value>::type* = nullptr>
void f() { }

Now, I saw in cppreference the new syntax, much cleaner in my opinion : typename = std::enable_if_t<std::is_same<int, T>::value>>

I wanted to port my code :

template<typename T,
         typename = std::enable_if_t<std::is_same<int, T>::value>>
void g() { }

template<typename T,
         typename = std::enable_if_t<std::is_same<double, T>::value>>
void g() { }

But now GCC (5.2) complains :

error: redefinition of 'template<class T, class> void g()'
       void g() { }

Why is that so ? What can I do to have the new, more concise syntax in this case if this is possible ?


Solution

  • Let's remove some code.

    template<
      class T,
      class U/* = std::enable_if_t<std::is_same<int, T>::value>*/
     >
    void g() { }
    
    template<
      class T,
      class U/* = std::enable_if_t<std::is_same<double, T>::value>*/
     >
    void g() { }
    

    would you be surprised if the compiler rejected the two above templates?

    They are both template functions of "type" template<class,class>void(). The fact that the 2nd type argument has a different default value matters not. That would be like expecting two different print(string, int) functions with different default int values to overload. ;)

    In the first case we have:

    template<
      typename T,
      typename std::enable_if<std::is_same<int, T>::value>::type* = nullptr
    >
    void f() { }
    
    template<
      typename T,
      typename std::enable_if<std::is_same<double, T>::value>::type* = nullptr
    >
    void f() { }
    

    here we cannot remove the enable_if clause. Updating to enable_if_t:

    template<
      class T,
      std::enable_if_t<std::is_same<int, T>::value, int>* = nullptr
    >
    void f() { }
    
    template<
      class T,
      std::enable_if_t<std::is_same<double, T>::value, int>* = nullptr
    >
    void f() { }
    

    I also replaced a use of typename with class. I suspect your confusion was because typename has two meanings -- one as a marker for a kind of template argument, and another as a disambiguator for a dependent type.

    Here the 2nd argument is a pointer, whose type is dependent on the first. The compiler cannot determine if these two conflict without first substituting in the type T -- and you'll note that they will never actually conflict.