I have the following code:
#include <iostream>
/*
template <class A, std::enable_if_t<!std::is_same_v<A, double>, bool> = true>
void test() {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
template <class A, std::enable_if_t<std::is_same_v<A, double>, bool> = true>
void test() {
std::cout << "SFINAE" << std::endl;
}
*/
template <class A, typename = std::enable_if_t<!std::is_same_v<A, double>>>
void test() {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
template <class A, typename = std::enable_if_t<std::is_same_v<A, double>>>
void test() {
std::cout << "SFINAE" << std::endl;
}
int main() {
test<int>();
test<double>();
}
Compiler complains
test_function_templ.cpp:21:6: error: redefinition of ‘template<class A, class> void test()’
21 | void test() {
| ^~~~
test_function_templ.cpp:16:6: note: ‘template<class A, class> void test()’ previously declared here
16 | void test() {
| ^~~~
test_function_templ.cpp: In function ‘int main()’:
test_function_templ.cpp:27:15: error: no matching function for call to ‘test<double>()’
27 | test<double>();
| ^
test_function_templ.cpp:16:6: note: candidate: ‘template<class A, class> void test()’
16 | void test() {
| ^~~~
test_function_templ.cpp:16:6: note: template argument deduction/substitution failed:
In file included from /opt/rh/devtoolset-10/root/usr/include/c /10/bits/move.h:57,
from /opt/rh/devtoolset-10/root/usr/include/c /10/bits/nested_exception.h:40,
from /opt/rh/devtoolset-10/root/usr/include/c /10/exception:148,
from /opt/rh/devtoolset-10/root/usr/include/c /10/ios:39,
from /opt/rh/devtoolset-10/root/usr/include/c /10/ostream:38,
from /opt/rh/devtoolset-10/root/usr/include/c /10/iostream:39,
from test_function_templ.cpp:1:
/opt/rh/devtoolset-10/root/usr/include/c /10/type_traits: In substitution of ‘template<bool _Cond, class _Tp> using enable_if_t = typename std::enable_if::type [with bool _Cond = false; _Tp = void]’:
test_function_templ.cpp:15:20: required from here
/opt/rh/devtoolset-10/root/usr/include/c /10/type_traits:2554:11: error: no type named ‘type’ in ‘struct std::enable_if<false, void>’
2554 | using enable_if_t = typename enable_if<_Cond, _Tp>::type;
| ^~~~~~~~~~~
If I use the first set of function templates (commented in the code) test()
, it compiles and runs as expected.
Questions
I thought for the 2nd set of function templates, calling
test<int>()
would instantiatetest<int, void>()
and callingtest<double>()
would instantiatetest<double, void>()
. But compiler seems like seeing two duplicate template functions?Why the first set of function templates doesn't have any issue while second set does?
CodePudding user response:
The problem of the first snippet is described here (see how /* WRONG */
vs /* RIGHT */
snippets of code map to your commented and uncommented code respectively).
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).
Here's my understanding of why that's the case.
When the compiler sees this (the correct version)
template <class A, std::enable_if_t<!std::is_same_v<A, double>, bool> = true>
void test() {}
template <class A, std::enable_if_t<std::is_same_v<A, double>, bool> = true>
void test() {}
it doesn't see a redeclaration, because it doesn't know if the two std::enable_if_t
will resolve to the same type. If it knew, then it'd be a hard compile time error, just like this is an error:
template <class A, bool = true>
void test() {}
template <class A, bool = false> // no matter the value; signature is the same
void test() {}
This doesn't exclude that the ambiguity can happen at the substitution level. For instance, there's no problem with these overload declarations, in principle
template <class A, std::enable_if_t<std::is_convertible_v<A, double>, bool> = true>
void test() {}
template <class A, std::enable_if_t<std::is_convertible_v<A, int>, bool> = true>
void test() {}
but as soon as you call test<int>()
, the ambiguity will pop up, because the compiler will be able to "successfully" instantiate each of the overloads, which both result in template<int, bool = whatever>
, which makes them ambiguous.
As regards the wrong version:
template <class A, typename = std::enable_if_t<!std::is_same_v<A, double>>>
void test() {}
template <class A, typename = std::enable_if_t<std::is_same_v<A, double>>>
void test() {}
the problem is that before even seeing how it's used, the compiler complains already, because default template arguments are not accounted for in function template equivalence. Indeed, a much simpler example shows the problem of the previous snippet:
template <class A, typename = typename A::foo>
void test() {}
template <class A, typename = typename A::bar>
void test() {}
Notice that, exactly like in the previous snippet, in this last snippet each of the overloads is correct by itself, until you try using it with a specific A
for which the expression of the default argument doesn't make sense, thus causing a hard error.
But if you put the two overloads together, that creates an ambiguity, because default template arguments are not accounted for in function template equivalence.