23

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?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Zhe Chen
  • 2,918
  • 4
  • 23
  • 39

5 Answers5

15

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.

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • 1
    what I previously write in the answer about "lvalue transformation" is plain nonsense.. I was half asleep when I wrote this answer. – Johannes Schaub - litb May 22 '16 at 21:02
  • BTW prior to http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1401 there was a useful note which stated "The binding of a reference to an expression that is reference-compatible with added qualification influences the rank of a standard conversion; see 13.3.3.2 [over.ics.rank] and 8.5.3 [dcl.init.ref].". I would have preferred if they kept this sentence and reworded. Note the wording "influences the rank of a standard conversion".. that's not random but emphasizes that it just affects the ranking, and does not constitute a conversion on its own. – Johannes Schaub - litb May 22 '16 at 21:09
5

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.

4

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.

Oktalist
  • 14,336
  • 3
  • 43
  • 63
2

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.

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • He could have expected that `char *const&` for an argment `char*` would need a const-conversion aswell. Intuitively, that's not too far off to expect, IMO. – Johannes Schaub - litb May 22 '16 at 12:46
  • 1
    Why `const T &s` with `T=char*` will go to `char* const&`? I mean why the position of 'const' changes. – Shangtong Zhang May 22 '16 at 12:48
  • @JohannesSchaub-litb, yes that's actually what I have expected. Both routines add low-level const to `s`. But why this conversion didn't happen here? – Zhe Chen May 22 '16 at 12:54
  • @ZheChen you expect that the second is more specialized. I think that is correct. But it only gets to comparing specialization level if overload resolution results in a tie. That is not the case here: from `char*` to `const char*` it's a pointer conversion, but from "lvalue" to "const lvalue", it's not actually a conversion. – Johannes Schaub - litb May 22 '16 at 13:05
  • @JohannesSchaub-litb I am still a little confusion. `char *` to `const char *` is a conversion, then `char * &` to `char * const &` is also a conversion, I think. Maybe your deleted answer could help explain this? – Zhe Chen May 22 '16 at 13:14
  • @ZheChen not in the standards world - that is not a conversion. In the mechanics of overload resolution, there is a step "lvalue transformation" which amounts to copy the argument or transforms an array to a pointer. An lvalue transformation is the cheapest of all tranformations. After that, comes the "value/qualification conversions/promotions". For your `chat * const&`, there isn't even an lvalue transformation, so it's *always* going to be "cheaper" than anything that has a conversion/promotion. – Johannes Schaub - litb May 22 '16 at 13:19
  • 1
    @SlardarZhang `const T&` means `T` will be const qualifed itself, for `char*` it'll be `char* const`, a const pointer, not `const char*`, a non-const pointer to const. See [What is the difference between const int*, const int * const, and int const *?](http://stackoverflow.com/q/1143262/3309790). – songyuanyao May 22 '16 at 13:20
  • @ZheChen Your example contains no `char * &` which could be converted. – Oktalist May 23 '16 at 13:21
1

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);
}