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
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