Reputation: 75874
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:
Upvotes: 8
Views: 981
Reputation: 31
To get things started:
// #1 Two templates with default type parameters.
template <typename T, typename T2 = int>
void Foo(T) {}
template <typename T, typename T2 = char> // Error – redefinition.
void Foo(T) {}
// #2 Two templates with default non-type parameters.
template <typename T, int = 0>
void Foo(T) {}
template <typename T, char = 'x'> // Allowed, but ambiguous for Foo<type>(value).
void Foo(T) {}
#1 and #2 are not equivalent. In the first two templates parameters T2 are similar (allowing any type), they are just defaulted with different value (type).
Furthermore standard states that:
The set of default template-arguments available for use is obtained by merging the default arguments from all prior declarations of the template in the same way default function arguments are (11.3.6). Example:
template<class T1, class T2 = int> class A;
template<class T1 = int, class T2> class A;
is equivalent to
template<class T1 = int, class T2 = int> class A;
Multiple defaulted types at the same positions cannot coexist.
With non-type parameters you essentially create separate templates with concrete types (something like specializations), which from the start creates separate signature for those functions.
constexpr bool true_1 = true;
constexpr bool true_2 = true;
// Shorter version of second parameter, nameless.
template <class T, std::enable_if_t<true_1>* = 0> auto foo(T) { return 1; }
template <class T, std::enable_if_t<true_2>* = 0> auto foo(T) { return 2; }
This didn't work because both constants are always effectively the same "true", yielding the same type for non-type parameter.
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>>* = 0> auto foo(T) { return 1; }
template <class T, std::enable_if_t<true_2<T>>* = 0> auto foo(T) { return 2; }
This example provides uncertainty factor - T
. Compiler cannot know what it will instantiate it with when it sees only declarations of your foo<>
functions. There might be specialization for true_2
that will point to false. Hence true_1
and true_2
are treated as different types.
Upvotes: 3