1

Having this:

#include <iostream>

void test(const int& ref){
    std::cout << "By reference\n";
}

void test(const int *ptr){
     std::cout << "By pointer\n";
}

template<typename T>
void test_template(const T &ref){
    std::cout << "By reference\n";
}

template<typename T>
void test_template(const T *ptr){
     std::cout << "By pointer\n";
}

int main() {
    int *p = 0;
    test(p);
    test_template(p);
    return 0;
}

I get the output (using g++ 4.8.4):

By pointer
By reference

Why does the template version behaves differently (is it a c++ deficit or a compiler bug) ?

  • See what happens when you remove the const in the template :-) – AndyG Dec 18 '15 at 15:34
  • 1
    Possible duplicate of [C++ operator lookup misunderstanding](http://stackoverflow.com/questions/34357559/c-operator-lookup-misunderstanding) You've been there. – LogicStuff Dec 18 '15 at 15:34

2 Answers2

1

It does not.

test_template(p);

calls

void test_template<int*>(int* const& p);

There is no ambiguity since void test_template<int>(const int* ptr) ranks lower in the compiler candidate list as p is a int*, not a const int*.

YSC
  • 38,212
  • 9
  • 96
  • 149
  • 2
    You can still pass an `int*` to a function taking `const int*`, it just needs to go through the process of adding the const, which ranks it a little bit lower. – chris Dec 18 '15 at 15:37
  • You're right. Is there an expression for that: `void test_template(const int* ptr)` _is not a first choice candidate_ or something similar? – YSC Dec 18 '15 at 15:39
  • 1
    @YSC "ranks lower" is the actual wording for this. – Quentin Dec 18 '15 at 15:42
  • @YSC, As with all overload resolution, it gets more complicated than you might like. The reference binding is an exact match, needing no conversions, which is in the "identity" conversions category. The standard explicitly says there's no conversion when binding a `cv T&` to a passed `T`. The pointer is also an exact match, specifically needing a qualification conversion, which is in the "qualification adjustment" category. Since they're both exact matches, there's a special rule basically saying that the first one's implicit conversion sequence is better because it's an identity conversion. – chris Dec 18 '15 at 15:51
  • I was actually wrong with my first comment. They rank the same (exact match). It's only because of the special rules for same-rank comparisons that the reference one is better. I was going from memory of how the pointer one was a bit further down in "the table" (see [over.ics.scs]). – chris Dec 18 '15 at 15:52
  • @chris My bad then. Could you quote the special rule for completeness please? Maybe you should answer the question and get the credit you deserve. – YSC Dec 18 '15 at 15:53
  • I've answered too many of these. For reference, [over.ics.rank] *Two implicit conversion sequences of the same form are indistinguishable conversion sequences unless one of the following rules applies:* … *Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence S2 if (3.2.1) — S1 is a proper subsequence of S2 (comparing the conversion sequences in the canonical form defined by 13.3.3.1.1, excluding any Lvalue Transformation; the identity conversion sequence is considered to be a subsequence of any non-identity conversion sequence)* – chris Dec 18 '15 at 15:57
  • Just to be confusing, there's a separate set of rules for when the two sequences have the same rank and this doesn't apply. Also, come to think of it, that might need to be "the identity conversion sequence is considered to be a **proper** subsequence", but I don't know if the proper is implied there. – chris Dec 18 '15 at 15:59
1

What's confusing you is that your templates are not exactly replicating the behavior of the overloads. When the compiler is performing (non-template) overload resolution, it finds that const int* is a better match than const int& because there is no conversion necessary for int* in the pointer overload (however, both have qualifier conversion, which is not as important).

However, for your templates things are a little different

This:

template<typename T>
void test_template(const T &ref){
    std::cout << "By reference\n";
}

can be instantiatated as const int& ref, yes, but it can also be instantiated as int* const& ref (Because T is const, and T is int*, and to make a const pointer, you place const after int*). Compare this to:

template<typename T>
void test_template(const T *ptr){
   std::cout << "By pointer\n";
}

where, T can be instantiated as int to obtain const int*

So now the compiler has to ask itself, which is a better match,

  • int* const& ref, or
  • const int* ptr?

And the answer is the first one. Given that references and cv qualifiers are removed before ordering templates, we're left comparing

  • int* against
  • const int*

So the second one requires a type transformation from int* to const int*, whereas the first one doesn't. (Pointer syntax is weird, you have remember that in our case above, the const that's left over is part of the type, not a qualifier). Note that if you had defined p as const int *p = 0; the compiler would have selected the second one.

To completely recreate the behavior of your non-template overloads, you'd have to explicitly test against T being a pointer type:

template<typename T, typename std::enable_if<!std::is_pointer<T>::value, int>::type = 0>
void test_template(const T& ref){
   std::cout << "By reference\n";
}
    
template<typename T>
void test_template(const T* ptr){
   std::cout << "By pointer\n";
}

However, I think what's "good enough" would really be to just move const to the other side of T* for the pointer overload:

template<typename T>
void test_template(const T &ref){
   std::cout << "By reference\n";
}
    
template<class T>
void test_template(T* const ptr){
   std::cout << "By pointer\n";
}

This works because first cv qualifiers and references are removed from types when ordering templates. That leaves us with

  • T ref versus

  • T* ptr

    and T* is considered more specialized than T, so the second one will be chosen, as desired.

Community
  • 1
  • 1
AndyG
  • 39,700
  • 8
  • 109
  • 143