#include <iostream>
#include <type_traits>
template<typename T,
typename = typename std::enable_if<std::is_pod<T>::value>::type
>
T foo(T t) { return t; }
template<typename T>
void foo(T t) { }
int main()
{
foo<int>(5);
}
main.cpp: In function 'int main()':
main.cpp:14:15: error: call of overloaded 'foo(int)' is ambiguous
foo<int>(5);
^
main.cpp:14:15: note: candidates are:
main.cpp:7:3: note: T foo(T) [with T = int; <template-parameter-1-2> = void]
T foo(T t) { return t; }
^
main.cpp:10:6: note: void foo(T) [with T = int]
void foo(T t) { }
^
Is it possible to solve The Error in The Code?
Constraining functions with SFINAE does not introduce an ordering, it just means a constrained function is callable or not callable, so when the first overload is enabled you have two ambiguous functions.
The simplest solution is to use the inverse of the same constraint to disable the second overload, so that only one is viable for a given type. To make that work you will need to move the enable_if
to the return type, rather than a default template argument (because default template arguments are not part of a function's signature and so can't be used for overloading, only constraining with SFINAE). This solution won't work for lots of overloads unless you can come up with a separate predicate (i.e. combination of type traits) for each overload such that none of the argument types matches more than one predicate. This means the predicates must be mutually exclusive, you cannot have a predicate testing a concept such as signed integral type that is a more refined version of another one testing a concept such as integral type, because both predicates would be true for int
and more than one function would be enabled.
One important reason that C++ Concepts are a better solution than SFINAE is that functions constrained by concepts are ordered, so that a more constrained function is a better match than a less constrained one. That means you don't need to play these SFINAE games and have mutually exclusive constraints, the compiler will Do The Right Thing.
Apply the same SFINAE to the alternate method, just the inverse of it (and move it to the return type); i.e. to disable the other one.
template<typename T>
typename std::enable_if<std::is_pod<T>::value, T>::type
foo(T t) { return t; }
template<typename T>
typename std::enable_if<!std::is_pod<T>::value, void>::type
foo(T t) { }
You mention there will be many overloads, so this may be cumbersome... your mileage may vary.
Note, why only 2? The original question was on 2, but the problem here is more the ordering between many overloads involving template constraints. This answer is left as a description of a more classic solution when there are only a few overloads (with more manageable SFINAE constraints).
The simplest solution is to disable the second function when the first is constrained.
More generally, you can use overload ranking to prefer a particular function, which scales better. It's something like this:
template<int N> struct choice : choice<N - 1> {};
template<> struct choice<0> {};
Here, the compiler has to perform N derived-to-base conversions to get from N to 0. So we can rank overloads from N to 0, where N is the most desirable and 0 the least desirable.
template<typename T,
typename = typename std::enable_if<std::is_pod<T>::value>::type
>
T foo(T t, choice<1>) { return t; }
template<typename T>
void foo(T t, choice<0>) { }
int main()
{
foo<int>(5, choice<1>());
}
Now you don't have to perform O(n^2) condition duplication.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With