Why does enable_if_t in template arguments complains about redefinitions?
Asked Answered
F

5

46

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 ?

Fender answered 19/7, 2015 at 10:57 Comment(11)
Your second code uses default template arguments. They are not part of the function signature, hence you're declaring two function templates with the same signature = redefinition. The corresponding use of enable_if_t is literally std::enable_if_t<std::is_same<int, T>::value>* = nullptr.Interdependent
Well, you didn't rewrite your original code exactly. You forgot the * = nullptr.Negrillo
You can add a dummy template parameter to one of the declarations, like , typename = void after enable_ifOeildeboeuf
@dyp: I think you can put your comment as an answer ! @KerrekSB: no, that's the point of enable_if_t @PiotrS. : okGroats
Probably a duplicate of: https://mcmap.net/q/373560/-sfinae-working-in-return-type-but-not-as-template-parameter-duplicate or of https://mcmap.net/q/373561/-enable_if-in-template-parameters-creates-template-redefinition-error or of https://mcmap.net/q/137835/-boost-enable_if-not-in-function-signatureInterdependent
I went the typename std::enable_if_t<std::is_same<int, T>::value>* = nullptr route. 4 characters gained, oh well :pGroats
@Jean-MichaëlCelerier You don't need the typename in front of std::enable_if_t. More characters gained.Inversely
Even more characters gained with std::enable_if_t<std::is_same<int, T>::value, int> = 0 or std::enable_if_t<std::is_same_v<int, T>, int> = 0.Cilium
@Cilium Just tried but my compiler (gcc 4.9) complains with *=0 and not with *=nullptr.Groats
That's why I didn't say *=0. I supplied int as the second argument to enable_if_t instead of the default void, so 0 is an integer literal, not a null pointer literal.Cilium
I should learn to read.Groats
O
59

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.

Oversubscribe answered 20/7, 2015 at 19:32 Comment(0)
C
12

enable_if_t<B> is just an alias for typename enable_if<B>::type. Let's substitute that in g so we can see the real difference between f and g:

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

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

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

In the case of f, we have two function templates both with template parameters <typename, X*>, where the type X is dependent on the type of the first template argument. In the case of g we have two function templates with template parameters <typename, typename> and it is only the default template argument which is dependent, so C++ considers that they are both declaring the same entity.

Either style can be used with the enable_if_t alias:

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

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

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() { }
Cilium answered 19/7, 2015 at 18:26 Comment(2)
If I could accept multiples answers I would have accepted yours as well because it is equally valid! thanks !Groats
No problem, Yakk's explanation is slightly better.Cilium
M
4

For a function return type, you're looking for the following:

template<typename T> std::enable_if_t< conditional, instantiation result > foo();

Example:

#include <iostream>

// when T is "int", replace with 'void foo()'   
template<typename T>
std::enable_if_t<std::is_same<int, T>::value, void> foo() {
    std::cout << "foo int\n";
}

template<typename T>
std::enable_if_t<std::is_same<float, T>::value, void> foo() {
    std::cout << "foo float\n";
}

int main() {
    foo<int>();
    foo<float>();
}

http://ideone.com/TB36gH

see also

http://ideone.com/EfLkQy

Moonlit answered 19/7, 2015 at 16:51 Comment(0)
S
1

Pretty late to the thread but since the idea behind the question was to have a cleaner syntax, I would like to mention another approach using function template specialization. It is not as general purpose as using enable_if but works well if all we want are different instantiations of f for different types.

template <typename T>
void f() = delete;

template <>
void f<int>() { }

template <>
void f<double>() { }

If the template is instantiated for any type besides int and double you get the following error message

<source>:18:5: error: call to deleted function 'foo'
   18 |     foo<char>();
      |     ^~~~~~~~~
<source>:4:6: note: candidate function [with T = char] has been explicitly deleted
    4 | void foo() = delete;
      |      ^
1 error generated.
Skyjack answered 30/12, 2023 at 21:45 Comment(0)
M
-7

You're missing a "::type" ..

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

    template<typename T,
             typename = std::enable_if_t<std::is_same<double, T>::value>::type>
    void g() { }
Monkey answered 19/7, 2015 at 13:39 Comment(1)
std::enable_if_t is an alias template of a nested ::type definition of std::enable_ifOeildeboeuf

© 2022 - 2024 — McMap. All rights reserved.