Search code examples
c++functiontemplatesoverloadingoverload-resolution

C++ Overloading functions and function templates - different behaviour?


I have the following code:

void A(const int*)
{
    cout << "const int*" << endl;
}

void A(const int&)
{
    cout << "const int&" << endl;
}

template <typename T>
void B(const T*)
{
    cout << "const T*" << endl;
}

template <typename T>
void B(const T&)
{
    cout << "const T&" << endl;
}

int main()
{
    int* a = nullptr;
    A(a);            //output: const int*

    int* b = nullptr;
    B(b);            //output: const T&

    return 0;
}

A(a) is invoking the function A(const int*)
B(b) is invoking the template function B(const T&)

I am not surprised from the template behaviour because of the way overload resolution works. But I cannot explain why the non-templated functions return the opposite result (which is kinda the more intuitive).

Is it because with the non-templated functions there is no need for the type to be deduced and is considered exact match (adding const-ness is permited?) ?

I am not an expert with meta programming and the things that the compiler is doing (like overload resolution) and that's why I am a bit confused.


Solution

  • In the call to the non-template, you are passing a pointer to an int. So how could it call the function which is expecting a reference to an int? The two are completely different types. And yes, adding const is permitted. If you had overloaded on constness though:

    void A(const int*)
    {
        cout << "const int*" << endl;
    }
    
    void A(int*)
    {
        cout << "int*" << endl;
    }
    

    The non-const version would be selected as a better match.

    With the template, things are different. Remember that pointers are types too. It is just as valid to deduce T as a pointer type. When you call B(b), the compiler can use this function:

    template <typename T>
    void B(const T*)
    {
        cout << "const T*" << endl;
    }
    

    In which case T must be deduced as int, and const T* becomes const int*, a pointer to a const int.

    Or the compiler can choose this function:

    template <typename T>
    void B(const T&)
    {
        cout << "const T&" << endl;
    }
    

    In which case T is deduced as int*. And const T& then becomes int* const&, that is, a reference to a const pointer to int.

    Since, in the second case, T maps exactly to what you actually passed in (a pointer to an int), it is the better match.