Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 569
linuxfever Avatar asked Sep 06 '16 18:09

linuxfever


People also ask

Can overload is possible in template?

You may overload a function template either by a non-template function or by another function template. The function call f(1, 2) could match the argument types of both the template function and the non-template function.

What is overload resolution?

The process of selecting the most appropriate overloaded function or operator is called overload resolution. Suppose that f is an overloaded function name. When you call the overloaded function f() , the compiler creates a set of candidate functions.

Can template be overloaded explain with an example?

The name of the function templates are the same but called with different arguments is known as function template overloading. If the function template is with the ordinary template, the name of the function remains the same but the number of parameters differs.

What is the difference between templates and overloading?

What is the difference between function overloading and templates? Both function overloading and templates are examples of polymorphism features of OOP. Function overloading is used when multiple functions do quite similar (not identical) operations, templates are used when multiple functions do identical operations.


1 Answers

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!
like image 190
Barry Avatar answered Nov 15 '22 12:11

Barry