An SFINAE technique is to to use a default class to enable/disable a function. However this doesn't work with overloading functions, resulting in "template parameter redefines default argument":
template <class T, class = std::enable_if_t<std::is_integral_v<T>>>
auto foo(T) { return 1; }
template <class T, class = std::enable_if_t<std::is_floating_point_v<T>>>
auto foo(T) { return 2; }
// error template parameter redefines default argument"
The common solution is to use default non-type template parameter:
template <class T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
auto foo(T) { return 1; }
template <class T, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
auto foo(T) { return 2; }
// works
This works only when the condition is "different". But understanding when the condition is "different" is not that simple.
Start with the most obvious example (identical (token by token) conditions):
template <class T, std::enable_if_t<std::is_integral_v<T>, T> = 0>
auto foo(T) { return 1; }
template <class T, std::enable_if_t<std::is_integral_v<T>, T> = 0>
auto foo(T) { return 2; }
// error template parameter redefines default argument"
With two different non-dependent conditions, that evaluate to the same value:
constexpr bool true_1 = true;
constexpr bool true_2 = true;
template <class T, std::enable_if_t<true_1, T> = 0>
auto foo(T) { return 1; }
template <class T, std::enable_if_t<true_2, T> = 0>
auto foo(T) { return 2; }
// error template parameter redefines default argument"
With two different dependant conditions, that evaluate to the same value:
template <class T> constexpr bool true_1 = true;
template <class T> constexpr bool true_2 = true;
template <class T, std::enable_if_t<true_1<T>, T> enable = 0>
auto foo(T) { return 1; }
template <class T, std::enable_if_t<true_2<T>, T> enable = 0>
auto foo(T) { return 2; }
// works
// (of course will give ambiguous call error when trying to call it,
// but the point here is you are allowed to declare them like this)
In this last example if I call foo
(i.e. foo(24)
) both overloads have the exact same parameters and template parameters:
error: call to 'foo' is ambiguous
return foo(12); ^~~
note: candidate function [with T = int, enable = 0]
auto foo(T) { return 1; } ^
note: candidate function [with T = int, enable = 0]
auto foo(T) { return 2; } ^
This seems to effectively instantiates two identical overloads (in terms of declaration, not definition).
All of my question are very closely related so I ask all of them here:
- What are the exact rules when two non-type template arguments are not considered a redefinition?
- How does the standard deal with two declaration-identical overloads (like in the last example)
- Why does this work for non-type template arguments, but not for type template arguments?