Why Does Enable_If_T in Template Arguments Complains About Redefinitions

Why does enable_if_t in template arguments complains about redefinitions?

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.

enable_if in template Parameters Creates Template Redefinition Error

It seems that the best solution here may be to use a slew of conditionals, which would prevent me from having to fool with template specializations:

template <typename T, typename R = std::conditional<sizeof(T) == sizeof(unsigned char),
unsigned char,
conditional<sizeof(T) == sizeof(unsigned short),
unsigned short,
conditional<sizeof(T) == sizeof(unsigned long),
unsigned long,
enable_if<sizeof(T) == sizeof(unsigned long long), unsigned long long>::type>::type>::type>::type>
R caster(T value){ return reinterpret_cast<R&>(value); }

My apologies to the reader cause it's like reading nested ternaries. However I'm currently unaware of a cleaner way to handle this.

This sadly still doesn't prevent the user from stomping on all my defaulting by providing his own second template parameter as mentioned by hvd.

EDIT:

I've asked another question here which has a solution that doesn't require placing the typename in the template definition and doesn't require declaring the type twice.

Issue with multiple overloads using enable_if

From cppreference, which has this very example as a Note:

A common mistake is to declare two function templates that differ only in their default template arguments. This does not work because the declarations are treated as redeclarations of the same function template (default template arguments are not accounted for in function template equivalence).

So what you need to do is not make the sfinae'd type a default argument. Instead, you could make it resolve to some type, e.g. int, and give it a default value, like this:

template <typename T, enable_if_t<is_same_v<T, int>, int> = 0>
void qw(T t)
{
std::cout << "int " << endl;
}

template <typename T, enable_if_t<is_same_v<T, float>, int> = 0>
void qw(T t)
{
cout << "float" << endl;
}

Why does SFINAE not work with std::enable_if_t?

Why you need SFINAE? A simple overload is enough!

template <typename T>
void f(const std::vector<T>& coll)
{
std::cout << "vector" << std::endl;
}

template < typename T>
void f(const std::list<T>& coll)
{
std::cout << "list" << std::endl;
}

int main()
{
std::vector<int> c1{ 1, 2, 3 };
std::list<int> c2{ 1, 2, 3 };
f(c1);
f(c2);
}

And if you really want to use SFINAE ( with no sense in that case ) you can fix your definition with:

template
<
typename T,
template<typename, typename = std::allocator<T>> class C,
typename std::enable_if_t
<
std::is_same<std::list<T>, C<T>>::value
>* = nullptr
>
void f(const C<T>& coll);

And why your definition did not work can be found already here:
SFINAE working in return type but not as template parameter

Template specialization and enable_if problems

Default template arguments are not part of the signature of a function template. So in your example you have two identical overloads of less, which is illegal. clang complains about the redefinition of the default argument (which is also illegal according to §14.1/12 [temp.param]), while gcc produces the following error message:

error: redefinition of 'template<class T, class> bool less(T, T)'

To fix the error move the enable_if expression from default argument to a dummy template parameter

template <class T,
typename std::enable_if<std::is_floating_point<T>::value, int>::type* = nullptr>
bool less(T a, T b) {
// ....
}

template <class T,
typename std::enable_if<std::is_integral<T>::value, int>::type* = nullptr>
bool less(T a, T b) {
// ....
}

Another option is to use enable_if in the return type, though I feel this is harder to read.

template <class T>
typename std::enable_if<std::is_floating_point<T>::value, bool>::type
less(T a, T b) {
// ....
}

template <class T>
typename std::enable_if<std::is_integral<T>::value, bool>::type
less(T a, T b) {
// ....
}

Correct way to define a covariant template function in C++

Depends on the check you actually want to do.

To check for a public, unambiguous base, use is_convertible on pointers:

template <class T>
auto foo(T& x) -> std::enable_if_t<std::is_convertible<T*, A*>{}/*, void*/> {
// stuff
}

To check for any base whatsoever, public, protected or private, ambiguous or unambiguous, use is_base_of:

template <class T>
auto foo(T& x) -> std::enable_if_t<std::is_base_of<A, T>{}/*, void*/> {
// stuff
}

How does changing a template argument from a type to a non-type make SFINAE work?

Mainly because [temp.over.link]/6 does not talk about template default argument:

Two template-heads are equivalent if their template-parameter-lists have the same length, corresponding template-parameters are equivalent, and if either has a requires-clause, they both have requires-clauses and the corresponding constraint-expressions are equivalent. Two template-parameters are equivalent under the following conditions:

  • they declare template parameters of the same kind,

  • if either declares a template parameter pack, they both do,

  • if they declare non-type template parameters, they have equivalent types,

  • if they declare template template parameters, their template parameters are equivalent, and

  • if either is declared with a qualified-concept-name, they both are, and the qualified-concept-names are equivalent.

Then by [temp.over.link]/7:

Two function templates are equivalent if they are declared in the same scope, have the same name, have equivalent template-heads, and have return types, parameter lists, and trailing requires-clauses (if any) that are equivalent using the rules described above to compare expressions involving template parameters.

... the two templates in your first example are equivalent, while the two templates in your second example are not. So the two templates in your first example declare the same entity and result in an ill-formed construct by [class.mem]/5:

A member shall not be declared twice in the member-specification, ...

SFINAE working in return type but not as template parameter

You should take a look at 14.5.6.1 Function template overloading (C++11 standard) where function templates equivalency is defined. In short, default template arguments are not considered, so in the 1st case you have the same function template defined twice. In the 2nd case you have expression referring template parameters used in the return type (again see 14.5.6.1/4). Since this expression is part of signature you get two different function template declarations and thus SFINAE get a chance to work.

How do I use std::is_pod in a template argument?

You need to use std::enable_if to use the value from std::is_pod in a SFINAE context. That would look like

// only enable this template if Z is a pod type
template <class Z, std::enable_if_t<std::is_pod_v<Z>, bool> = true>
void x()
{
std::cout << "yep" << std::endl;
}

// only enable this template if Z is not a pod type
template <class Z, std::enable_if_t<!std::is_pod_v<Z>, bool> = true>
void x()
{
std::cout << "nope" << std::endl;
}

Do note that std::is_pod is deprecated in C++17 and has been removed from C++20.



Related Topics



Leave a reply



Submit