Zhe Chen
Zhe Chen

Reputation: 2967

Template function overloading in C++

I have the following definition.

using namespace std;

template <typename T>
void foo(const T &s) {
    cout << 1;
}

template <typename T>
void foo(const T *s) {
    cout << 2;
}

int main(int argc, const char * argv[]) {
    char str[] =  "ss";
    char *s = str;
    foo(s);

    return 0;
}

Then it outputs

1

From my understanding, both versions have to go through a const conversion. Then void foo(const T *s) is more specialized and should be invoked. However the compiler chose void foo(const T& s). What is the explanation?

Upvotes: 23

Views: 3547

Answers (5)

user6366278
user6366278

Reputation:

The reason is because when the argument is a pointer, you have to pass in a pointer.. Like this:

// Example program
#include <iostream>
#include <string>

using std::cout;
using std::endl;

void foo(int *a)
{
  cout << *a << endl;
}

int main()
{
  int a = 5;
  foo(&a);
}

But if your argument is a refrence you can just pass the parameter as it is like this:

// Example program
#include <iostream>
#include <string>

using std::cout;
using std::endl;

void foo(int &a)
{
  cout << a << endl;
}

int main()
{
  int a = 5;
  foo(a);
}

Upvotes: 1

Johannes Schaub - litb
Johannes Schaub - litb

Reputation: 507373

Some people have pointed out that the parameters of the templates as chosen by the compiler

void f(char * const&)
void f(const char *);

Here, notice that the compiler expects a pointer to char, char*, for the first function and it's a const reference. It may come as a surprise, if you found that for your case it prefers the first template, but for the following two, it will prefer the second

template <typename T>
void foo(const T& s) {
    cout << 1;
}

template <typename T>
void foo(T &s) {
    cout << 2;
}

So, of course, it will look at the const sometimes. Why didn't it in your case? Because it will only look at the const for a reference if the other function has also a reference.

In your case, from char* to const char* it's a pointer conversion, but from lvalue to const lvalue, it's not actually a conversion. Adding a const by a const reference is ignored by overload resolution except when both functions have reference parameters as in the above case.

Upvotes: 15

songyuanyao
songyuanyao

Reputation: 173034

both versions have to go through a const conversion.

The 1st one don't need conversion. For template parameter const T& with template argument char *, T will be deduced as char* and then the type of function parameter will be char* const &, so it's perfect match. The function argument will be bound to reference to const (T -> const T&, i.e. char* to char* const&), const qualified is not conversion.

For the 2nd one, the template parameter const T* with template argument char *, T will be deduced as char and then the type of function parameter will be const char*, and qualification conversion is needed to convert char* to const char*.

From your comment

Both routines add low-level const to s.

The 1st one is adding const to s, but the 2nd one isn't. It's adding const to what s points to, not s itself. This is the difference.

Upvotes: 2

Oktalist
Oktalist

Reputation: 14734

I have moved the const without changing the semantics of the code, just so you are not surprised when the position of const "changes" later.

template <typename T>
void foo(T const &s) {
    cout << 1;
}

template <typename T>
void foo(T const *s) {
    cout << 2;
}

char *x = "x";
foo(x);

Overload 1 will have to deduce T to be char* so the type of s will be char * const & (reference to const pointer to non-const char). Such a reference can bind to the argument type (char *; pointer to non-const char) without any conversion.

Overload 2 will have to deduce T to be char so the type of s will be char const * (pointer to const char). This incurs a qualification conversion from the argument type (char *; pointer to non-const char) to the parameter type (char const *; pointer to const char).

A more illustrative example of the principle in overload 1 is the following:

int n = 42;
int const &i = n;

Binding a const reference to a non-const entity doesn't involve a conversion, because it's the reference that adds the qualification, it's not the qualification being added to the entity in order to match it to the reference type.

Upvotes: 4

user6367439
user6367439

Reputation: 51

The reason is because s is a non-const pointer, so int* const& is actually a better match than int const* because it doesn't have to add const to the pointer type.

If s were const qualified then it would be an exact match for the T const* version.

Upvotes: 5

Related Questions