简体   繁体   中英

Overload resolution with template parameters

I am having trouble understanding why the following leads to an ambiguous call:

#include <iostream>

// generic version f(X, Y)
template <class X, class Y>
void f(X x, Y y) {
    std::cout << "generic" << std::endl;
}

// overload version
template <class X>
void f(X x, typename X::type y) {
    std::cout << "overload" << std::endl;
}

struct MyClass {
    using type = int;
};

int main() {
    f(MyClass(), int()); // Call to f is ambiguous
}

I would expect the overload version, which is more specialised in the second argument than the generic version, to be selected as the best candidate. I know that if I change the overload version to

template <class X>
void f(X x, int y) {
    std::cout << "overload" << std::endl;
}

then the call is resolved just fine, which means it has to do with the fact that X::type is a template dependent name, but still cannot work out why it fails. Any help is much appreciated.

First, we pick the viable candidates. Those are:

void f(MyClass, int);                    // with X=MyClass, Y=int
void f(MyClass, typename MyClass::type); // with X=MyClass

Those candidates both take the same arguments, so have equivalent conversion sequences. So none of the tiebreakers based on those apply, so we fall back to the last possible tiebreaker in [over.match.best]:

Given these definitions, a viable function F1 is defined to be a better function than another viable function F2 if for all arguments i, ICSi(F1) is not a worse conversion sequence than ICSi(F2), and then [...] F1 and F2 are function template specializations, and the function template for F1 is more specialized than the template for F2 according to the partial ordering rules described in 14.5.6.2.

So we try to order the two function templates based on the partial ordering rules, which involve synthesizing a unique type for each template parameter and attempting to perform template deduction against each over overload. But with a key additional relevant rule from [temp.deduct.partial]:

Each type nominated above from the parameter template and the corresponding type from the argument template are used as the types of P and A. If a particular P contains no template-parameters that participate in template argument deduction, that P is not used to determine the ordering.

So what does this mean. First, let's try to deduce the generic version from the overload. We pick synthetic types UniqueX (for X ) and UniqueX_type (for typename X::type ) and see if we can invoke the generic function. This succeeds (with X=UniqueX and Y=typename X::type ).

Let's try the inverse. We pick a UniqueX (for X ) and a UniqueY (for Y ) and try to perform template deduction. For the first P/A pair, this trivially succeeds. But for the second argument, X is a non-deduced context, which you would think would mean that template deduction fails. BUT as per the bolded part of the quote, we just skip this P/A pair for the purposes of ordering. So, since the first P/A pair succeeded, we consider the entire deduction process to have succeeded.

Since template deduction succeeds in both directions, we cannot pick one function or the other as being more specialized. Since there are no further tiebreakers, there is no single best viable candidate, so the call is ambiguous.


When the second overload is changed to:

template <class X> void f(X, int);

the part of the process that changes is that deduction now fails in one direction. We can deduce X=UniqueX but the second pair has a parameter of type int and an argument of type UniqueY , which will not work, so this direction fails. In the reverse direction, we can deduce X=UniqueX and Y=int . That makes this overload more specialized that the generic overload, so it would be preferred by the last tiebreaker I originally mentioned.


As an addendum, note that partial ordering of templates is complicated. Consider:

template <class T> struct identity { using type = T; };

template <class T> void foo(T );                             // #1
template <class T> void foo(typename identity<T>::type );    // #2

template <class T> void bar(T, T);                           // #3
template <class T> void bar(T, typename identity<T>::type ); // #4

foo(0);      // calls #1, #2 isn't even viable
foo<int>(0); // calls #2
bar(0,0);    // calls #3! we fail to deduce 3 from 4, but we succeed
             // in deducing 4 from 3 because we ignore the second P/A pair!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM