Partial ordering of function templates – ambiguous call

I’m posting the details of my current understanding of the issue as an answer. I’m not sure it will be the final word on this, but it could serve as a basis for further discussion if needed. The comments from dyp, hvd and Columbo have been essential for finding the various bits of information referenced below.

As I suspected, the problem is with the rules for partial ordering of function templates. Section [14.8.2.4] (Deducing template arguments during partial ordering) says that, after the preliminary transformations that remove references and cv-qualifiers, type deduction is done as described in [14.8.2.5] (Deducing template arguments from a type). That section is different from the one that refers to function calls – that would be [14.8.2.1] (Deducing template arguments from a function call).

When template parameters are deduced from function argument types, there are a few special cases that are allowed; for example, a template parameter T used in a function parameter of type T* can be deduced when the function argument is T[i], because the array-to-pointer conversion is allowed in this case. However, this is not the deduction process that’s used during partial ordering, even though we’re still talking about functions.

I guess the easy way to think about the rules for template argument deduction during partial ordering is to say they’re the same rules as for deducing template arguments when matching class template specializations.

Clear as mud? Perhaps a couple of examples will help.

This works, because it uses the rules for deducing template arguments from a function call:

#include <iostream>
#include <type_traits>

template<typename T> void f(T*)
{
    std::cout << std::is_same<T, int>::value << '\n';
}

int main()
{
    int a[3];
    f(a);
}

and prints 1.

This doesn’t, because it uses the rules for deducing template arguments from a type:

#include <iostream>

template<typename T> struct A;

template<typename T> struct A<T*>
{
    static void f() { std::cout << "specialization\n"; }
};

int main()
{
    A<int[3]>::f();
}

and the error from Clang is

error: implicit instantiation of undefined template 'A<int [3]>'

The specialization cannot be used, because T* and int[3] don’t match in this case, so the compiler tries to instantiate the primary template.

It’s this second kind of deduction that’s used during partial ordering.


Let’s get back to our function template declarations:

template<typename T> void f(T, const char*); //#1
template<std::size_t N> void f(int, const char(&)[N]); //#2

My description of the process for partial ordering becomes:

  • Template argument deduction with #1 as parameter and #2 as argument: we invent a value M to substitute for N, T is deduced as int, but a parameter of type const char* does not match an argument of type char[M], so #2 is not at least as specialized as #1 for the second pair of types.
  • Template argument deduction with #2 as parameter and #1 as argument: we invent a type U to substitute for T, int and U do not match (different types), a parameter of type char[N] does not match an argument of type const char*, and the value of the non-type template parameter N cannot be deduced from the arguments, so #1 is not at least as specialized as #2 for either pair of types.

Since, in order to be chosen, a template needs to be at least as specialized as the other for all types, it follows that neither template is more specialized than the other and the call is ambiguous.


The explanation above goes somewhat against the description of a similar problem in Core Language Active Issue 1610 (link provided by hvd).

The example in there is:

template<class C> void foo(const C* val) {}
template<int N> void foo(const char (&t)[N]) {}

The author argues that, intuitively, the second template should be chosen as more specialized, and that this doesn’t currently happen (neither template is more specialized than the other).

He then explains that the reason is the removal of the const qualifier from const char[N], yielding char[N], which causes deduction to fail with const C* as parameter.

However, based on my current understanding, deduction would fail in this case, const or no const. This is confirmed by the current implementations in Clang and GCC: if we remove the const qualifier from the parameters of both function templates and call foo() with a char[3] argument, the call is still ambiguous. Arrays and pointers simply don’t match according to the current rules during partial ordering.

Having said that, I’m not a member of the committee, so there may be more to this than I currently understand.


Update: I’ve recently stumbled upon another Core active issue that goes back to 2003: issue 402.

The example in there is equivalent to the one in 1610. The comments on the issue make it clear that the two overloads are unordered according to the partial ordering algorithm as it stands, precisely because of the lack of array-to-pointer decay rules during partial ordering.

The last comment is:

There was some sentiment that it would be desirable to have this case
ordered, but we don’t think it’s worth spending the time to work on it
now. If we look at some larger partial ordering changes at some point,
we will consider this again.

Thus, I’m pretty confident that the interpretation I gave above is correct.

Leave a Comment