Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SFINAE works differently in cases of type and non-type template parameters

Why does this code work:

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

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

and can correctly distinguish between these two calls:

Add(1);
Add(1.0);

while the following code if compiled results in the redefinition of Add() error?

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

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

So if the template parameter is type, then we have redefinition of the function, if it is non-type, then everything is ok.

like image 942
undermind Avatar asked Apr 08 '16 11:04

undermind


People also ask

Which parameter is allowed for non-type template?

Which parameter is legal for non-type template? Explanation: The following are legal for non-type template parameters:integral or enumeration type, Pointer to object or pointer to function, Reference to object or reference to function, Pointer to member.

Can we use non-type parameters as argument templates?

A non-type template argument provided within a template argument list is an expression whose value can be determined at compile time. Such arguments must be constant expressions, addresses of functions or objects with external linkage, or addresses of static class members.

What is the difference between template Typename T and template T?

There is no difference. typename and class are interchangeable in the declaration of a type template parameter.

What is template type parameter?

A template parameter is a special kind of parameter that can be used to pass a type as argument: just like regular function parameters can be used to pass values to a function, template parameters allow to pass also types to a function.


4 Answers

SFINAE is about substitution. So let us substitute!

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

Becomes:

template<   class T=int,    int* = nullptr> void Add(int) {}  template<   class T=int,    Substitution failure* = nullptr> void Add(int) {  template<   class T=double,    Substitution failure* = nullptr> void Add(double) {}  template<   class T=double   double* = nullptr> void Add(double) {} 

Remove failures we get:

template<   class T=int,    int* = nullptr> void Add(int) {} template<   class T=double   double* = nullptr> void Add(double) {} 

Now remove template parameter values:

template<   class T,    int*> void Add(T) {} template<   class T   double*> void Add(T) {} 

These are different templates.

Now the one that messes up:

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

Becomes:

template<   typename T=int,    typename =int> void Add(int) {}  template<   typename int,    typename = Substitution failure > void Add(int) {}  template<   typename T=double,    typename = Substitution failure > void Add(double) {}  template<   typename T=double,    typename = double> void Add(double) {} 

Remove failures:

template<   typename T=int,    typename =int> void Add(int) {} template<   typename T=double,    typename = double> void Add(double) {} 

And now template parameter values:

template<   typename T,    typename> void Add(T) {} template<   typename T,    typename> void Add(T) {} 

These are the same template signature. And that is not allowed, error generated.

Why is there such a rule? Beyond the scope of this answer. I'm simply demonstrating how the two cases are different, and asserting that the standard treats them differently.

When you use a non-type template parameter like the above, you change the template signature not just the template parameter values. When you use a type template parameter like the above, you only change the template parameter values.

like image 123
Yakk - Adam Nevraumont Avatar answered Sep 19 '22 14:09

Yakk - Adam Nevraumont


Here, the problem is that the template signature for add() is the same: A function template taking two parameters type.

So when you write:

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

That's fine, but when you write:

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

You are redefining the first add() template, only this time you specify a different default type for the second template parameter: in the end, you defined an overload for add() with the exact same signature, hence the error.

If you want to have several implementations like your question suggests, you should use std::enable_if_t as the return parameter of your template or use it in the same fashion as your first example. So your initial code becomes:

template<typename T> std::enable_if_t<std::is_same<T, int>::value> Add(T) {} template<typename T> std::enable_if_t<!std::is_same<T, int>::value> Add(T) {} 

Working example on Coliru

On the code above, if T == int, the second signature becomes invalid and that triggers SFINAE.

NB: Suppose you want N implementations. You can use the same trick as above but you'll need to make sure only one boolean among the N is true and the N-1 that remain are false, otherwise you'll get the exact same error!

like image 39
Rerito Avatar answered Sep 20 '22 14:09

Rerito


I think the problem is the fact that you can use a function even if a default template parameter does not compile by specifying a different value for it. Think of what would happen if you specified two template parameters in a call to add.

like image 26
Dani Avatar answered Sep 19 '22 14:09

Dani


I'll try to first give an example without the use of templates, but with default arguments. The following example is comparable to why the second example of yours fails, although it is not indicative of the inner workings of template overload resolution.

You have two functions declared as such:

void foo(int _arg1, int _arg2 = 3);

And

void foo(int _arg1, int _arg2 = 4);

Hopefully you realize that this will fail to compile, their is never going to be a way to distinguish between the two calls to foo with the default argument. It's completely ambiguous, how will the compiler ever know which default to choose? You may wonder why i used this example, after all shouldn't the template in the first example deduce different types? The short answer to that is no, and that's because the "signature" of the two templates in your second example:

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

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

...have the exact same signature, which is:

template<typename T,typename>
void Add(T);

And (respectively)

template <typename T, typename>
void Add(T); 

Now you should start to understand the similarity's between the example i gave with non-templates and the example you provided that failed.

like image 39
Nowhere Man Avatar answered Sep 17 '22 14:09

Nowhere Man