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]
2 Partial ordering selects which of two function templates is more
specialized than the other by transforming each template in turn (see
next paragraph) and performing template argument deduction using the
function type. The deduction process determines whether one of the
templates is more specialized than the other. If so, the more
specialized template is the one chosen by the partial ordering
process.
3 To produce the transformed template, for each type,
non-type, or template template parameter (including template parameter
packs (14.5.3) thereof) synthesize a unique type, value, or class
template respectively and substitute it for each occurrence of that
parameter in the function type of the template.
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
const A<0, typename std::tuple_element<0, Arg1>::type>&
Your second overload is transformed to the following synthesized 2nd argument
const A<
std::tuple_size<Arg1>::value-1, typename
std::tuple_element<std::tuple_size<Arg1>::value-1, Arg1>::type
>&
Your third overload is transformed to the following synthesized 2nd argument
const A<Arg2, typename std::tuple_element<Arg2, Arg1>::type>&
14.8.2.4 Deducing template arguments during partial ordering [temp.deduct.partial]
2 Two sets of types are used to determine the partial ordering. For
each of the templates involved there is the original function type and
the transformed function type. [ Note: The creation of the transformed
type is described in 14.5.6.2. — end note ] The deduction process uses
the transformed type as the argument template and the original type of
the other template as the parameter template. This process is done
twice for each type involved in the partial ordering comparison: once
using the transformed template-1 as the argument template and
template-2 as the parameter template and again using the transformed
template-2 as the argument template and template-1 as the parameter
template.
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]
5 The non-deduced contexts are:
— ...
— A non-type template argument or an array bound in which a subexpression references a template parameter.
— ...
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.
template <typename Tuple, int N>
typename std::enable_if<N == std::tuple_size<Tuple>::value-1, double>::type
result(const Tuple& t, const A<N, typename std::tuple_element<N, Tuple>::type>& a)
{
return 1;
}
template <typename Tuple, int N>
typename std::enable_if<N != std::tuple_size<Tuple>::value-1, double>::type
result(const Tuple& t, const A<N, typename std::tuple_element<N, Tuple>::type>& a)
{
return 0.5;
}
Live Example.