Search code examples
c++templatesoverload-resolution

Why compiler cannot decide which function to call without the reference operator?


Below you can see small function called max.

template <typename T>
T max(T& arg1, T& arg2) {    
    if (arg1 > arg2) {
        return arg1;
    } else {
        return arg2;
    }
}

When i call this function inside main, it perfectly works, but if i make remove reference from arguments

T max(T arg1, T arg2)  

Then compiler gives following error

main.cpp:18:21: error: call of overloaded 'max(int&, int&)' is ambiguous

It is clear that compiler could not decide whether to call my function or standard function. The question here how it can decide and work fine, when there is reference on the head of arguments ?

When i call the function with constant parameters

const int a = 12;
const int b = 24;
cout << max(a, b) << endl;

it calls the standard max function. But when i call with non const values, it calls my function.

I can understand that if the object is const, the the const function will be called elsewhere the non-const function will be called. But why it should be reference to trigger this mechanism ? Why it cannot decide without the reference operator is there ?


Solution

  • This is a question about overload resolution, which nobody has yet addressed. Let's ignore templates for the moment, since they're not strictly relevant, and let's pretend we these overloads declared:

    void foo(int& );       // (1)
    void foo(const int&);  // (2)
    

    What happens when we try to call foo with an lvalue?

    int i;
    foo(i);
    

    Both candidates are viable. The first rule for determining which is the best viable candidate is conversion sequences, and [over.ics.rank]:

    Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence S2 if
    — [...]
    — S1 and S2 are reference bindings (8.5.3), and the types to which the references refer are the same type except for top-level cv-qualifiers, and the type to which the reference initialized by S2 refers is more cv-qualified than the type to which the reference initialized by S1 refers.

    S1 and S2 are both reference bindings, and the types to which the references refer (int and const int) are the same except for cv-qualifiers, but S2's referred type is more cv-qualified than S1's. The least-qualified reference wins. Hence, (1) is preferred.

    This is exactly why your function template

    template <typename T> T max(T&, T&); // (1)
    

    is preferred to the standard function template:

    template <typename T> T const& max(T const&, T const&); // (2)
    

    The second part of the question introduces this additional overload:

    void foo(int& );       // (1)
    void foo(const int&);  // (2)
    void foo(int );        // (3)
    

    We know that (1) is better than (2), but there does not exist a rule differentiating between (1) and (3). There is no "reference is better than non-reference" or vice-versa rule. Thus, both candidates are equally viable - which is ill-formed. Hence the error about ambiguity. This is why:

    template <typename T> T max(T, T); // (3)
    template <Typename T> T const& max(T const&, T const&); // (2)
    

    didn't compile for you.