I would think that the following code should be working, but both g++ and clang++ return the exact same error (although Visual C++ 2012 doesn't).
#include <iostream>
#include <tuple>
template <int N, typename T>
struct A { };
template <typename Tuple>
double result(const Tuple& t, const A<0, typename std::tuple_element<0, Tuple>::type>& a)
{
return 0;
}
template <typename Tuple>
double result(const Tuple& t, const A<std::tuple_size<Tuple>::value-1,
typename std::tuple_element<std::tuple_size<Tuple>::value-1,Tuple>::type>& a)
{
return 1;
}
template <typename Tuple, int N>
double result(const Tuple& t, const A<N, typename std::tuple_element<N, Tuple>::type>& a)
{
return 0.5;
}
int main()
{
auto a = std::make_tuple(0, 1, 2., 3., 4);
std::cout << result(a, A<0,int>()) << std::endl;
std::cout << result(a, A<2,double>()) << std::endl;
std::cout << result(a, A<4,int>()) << std::endl; // Fails if uncommented
return 0;
}
The error is due to the last line and the fact that the second and third result
functions are considered equivalent. Although I would think the second one is a better fit than the third one (like the first one is).
I am not sure though. Can anybody tell me if I am wrong or if the compiler is?
TLDR; The reason that your program fails to compile is that the second and third overload are equally good matches during overload resolution. In particular, neither is more specialized than the other. Because overload resolution cannot select a best match, the program is ill-formed. The cure is to SFINAE your way out of it.
The problem
14.5.6.2 Partial ordering of function templates [temp.func.order]
For all three overloads, the first synthesized argument is equal, and since all arguments are considered one by one, we can focus on the second one.
Your first overload is transformed to the following synthesized 2nd argument
Your second overload is transformed to the following synthesized 2nd argument
Your third overload is transformed to the following synthesized 2nd argument
14.8.2.4 Deducing template arguments during partial ordering [temp.deduct.partial]
It is clear that the first and second overload have no 2nd template parameter to deduce and so they are at least as specialized as the third overload. The question is whether the third can have it's
N
parameter deduced from the first and second overloads' synthesized 2nd argument.For the first overload, this is true for
N=0
, and so the first overload is more specialized than the third. This is why your first function call selects the first overload.For the third overload, argument deduction does not take place it is a non-deduced context:
14.8.2.5 Deducing template arguments from a type [temp.deduct.type]
This means that the third overload is also at least as specialized as the second one. Hence, overload resolution is not able to select one, and the program is ill-formed.
The cure
Simply make two overloads with a non-overlapping condition inside an
enable_if
(using SFINAE). This bypasses overload resolution in this case.Live Example.
You should replace your overloads with some tag dispatching.
Write one function, then check if the second arg
A
is_same
as the first type in the tuple in a static way, calling another function with a type dependent on that. Repeat for last on the false branch.with maybe some
decay
orremove_const
in there.The idea is that
std::is_same<X,Y>
istrue_type
if they are the same, andfalse_type
otherwise.helper
overloads the third argument on both true and false type, giving you a compile time branch. Repeat again for the last type, and you are done.In the second overload, the
std::tuple_size<Tuple>::value-1
part depends on the template parameterTuple
and is hence not a better match or, in C++ speak, "more specialized". This is why it is considered equal wrt overloading with the third one which explicitly hasN
.Only your first overload uses a constant value of
0
which is not dependent onTuple
and is therefore a better match.In case you want to solve your problem, you can disable the third overload for when it would match the second one: