Consider the following:
template <class T> void Foo(const T* x) {
std::cout << "I am the pointer overload" << std::endl;
}
template <class T> void Foo(const T& x) {
std::cout << "I am the reference overload" << std::endl;
}
Given the above, I would expect the following to call the pointer overload:
int* x;
Foo(x);
but it doesn't. That seems odd to me as a const T*
can clearly bind to a non-const T
just as well as a const T&
can but the pointer variant seems like a "better fit".
For my application I want the pointer variant to be called. I can make that work via an additional specialization:
template <class T> void Foo(T* x) {
const T* const_x = x;
Foo(const_x);
}
but that feels wrong and unnecessary. Is there a better way? What am I not understanding (other than section x.y.z of the standard says it's this way)?
You are thinking: If my parameter would not be constant (or if the argument would be constant) then the pointer would be a perfect match. That is correct. The difference in what I have is the const
. So just adding const
to the above scenario I should get the pointer overload as well. That is also correct. Now how can that be since you clearly get the reference overload? Well, the code doesn't correspond to your thought. Here is the code that would go along with your line of thinking and that would indeed select the pointer overload:
template <class T> void Foo(T* const x);
template <class T> void Foo(T const &x);
Pay close attention to the place of const
. I have added const
as a top level qualifier. While T const &x
is equivalent with what you have const T& x
, T* const x
is not the same as const T* x
.
Lets see the overload resolution in this case:
fun | T | parameter | argument
-----------------------------------------+------+--------------+-----------
template <class T> void Foo(T* const x) | int | int* const | int*
template <class T> void Foo(T const &x) | int* | int* const & | int*
Lets see the overload with your version:
fun | T | parameter | argument
-----------------------------------------+------+--------------+-----------
template <class T> void Foo(const T* x) | int | const int* | int*
template <class T> void Foo(T const &x) | int* | int* const & | int*
As you can see in the first version just adding a top level const is preferred and the pointer overload is chosen. There is no pointer conversion required.
In the second case the pointer overload would require a pointer conversion from pointer to mutable
to pointer to const
. Those are different types of pointers. But with the 2nd overload there is no pointer conversion required. Just adding a top level const
.
This is in a nutshell the best I can explain without going that's what x.y.z section of the standard says
Your problem is that when compiling Foo(x);
a template type deduction will be executed, and as your x is an int*
this will be interpreted first as a Foo<int*>(x)
call and your template <class T> void Foo(const T& x)
overload is a perfect match for this, so type deduction ends here.
If you would deal with classes (instead of template functions) you could use partial template specializations to distinguish pointer-types and non-pointer-types but for functions only full specializations are allowed.
What you can do is to use SFINAE techniques like:
template <class T> void Foo(const T* x) {
std::cout << "I am the pointer overload" << std::endl;
}
template <class T>
typename std::enable_if<!std::is_pointer<T>::value>::type
Foo(const T& x) {
std::cout << "I am the reference overload" << std::endl;
}
In this case the reference overload will not match (when deducting types) if T is a a pointer-type, so the compiler will try Foo<int>(x)
as well and your template <class T> void Foo(const T* x)
overload will be a perfect match (just what you have expected).
sample:
int x = 12;
int* pX = new int(5);
Foo(x);
Foo(pX);
output:
I am the reference overload
I am the pointer overload
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With