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 conditional
s, 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
Manual for Cross-Compiling a C++ Application from Linux to Windows
C++ Streams Confusion: Istreambuf_Iterator VS Istream_Iterator
Delete All Items from a C++ Std::Vector
Serial Port (Rs -232) Connection in C++
Exclude Source File in Compilation Using Makefile
How to Encode a String to Base64 Using Only Boost
Lru Implementation in Production Code
Simple Ipc Between C++ and Python (Cross Platform)
How to Use Threads to Speed Up File Reading
Template Function Inside Template Class
Localtime VS Localtime_S and Appropriate Input Arguments
How to Find the Name of the Calling Function
How to Call Function After Window Is Shown
What Can Make C++ Rtti Undesirable to Use
How to Use C++ Std::Ostream with Printf-Like Formatting
Class Template for Numeric Types