The following code demonstrates the core of a C++ template metaprogramming pattern I have been using to determine whether a type T
is an instantiation of a specific class template:
#include <iostream>
template<class A, class B>
struct S{};
template<class A, class B>
constexpr bool isS(const S<A,B>*) {return true;}
template<class T>
constexpr bool isS(const T*) {return false;}
int main() {
S<int,char> s;
std::cout<<isS(&s)<<std::endl;
return 0;
}
It features two overloads of a constexpr
function template isS
, and it outputs 1
, as expected. If I remove the pointer from the second isS
, i.e. replace it with
template<class T>
constexpr bool isS(const T) {return false;}
the program unexpectedly outputs 0
. If both versions of isS
make it through to the overload resolution phase of compilation, then the output implies that the compiler is choosing the second overload. I have tested this under GCC, Clang and vc++ using the online compilers here, and they all produce the same result. Why does this happen?
I have read Herb Sutter's "Why Not Specialize Function Templates" article several times, and it seems that both isS
functions should be considered to be base templates. If this is so, then it is a question of which one is the most specialised. Going by intuition and this answer, I would expect the first isS
to be the most specialised, because T
can match every instantiation of S<A,B>*
, and there are many possible instantiations of T
that cannot match S<A,B>*
. I'd like to locate the paragraph in the working draft that defines this behaviour, but I'm not entirely sure what phase of compilation is causing the problem. Is it something to do with "14.8.2.4 Deducing template arguments during partial ordering"?
This issue is particularly surprising given that the following code, in which the first isS
takes a reference to const S<A,B>
and the second takes a const T
, outputs the expected value 1
:
#include <iostream>
template<class A, class B>
struct S{};
template<class A, class B>
constexpr bool isS(const S<A,B>&) {return true;}
template<class T>
constexpr bool isS(const T) {return false;}
int main() {
S<int,char> s;
std::cout<<isS(s)<<std::endl;
return 0;
}
So the problem seems to be something to do with how pointers are treated.
You may overload a function template either by a non-template function or by another function template. The function call f(1, 2) could match the argument types of both the template function and the non-template function.
A template function can be overloaded either by a non-template function or using an ordinary function template.
The process of selecting the most appropriate overloaded function or operator is called overload resolution. Suppose that f is an overloaded function name. When you call the overloaded function f() , the compiler creates a set of candidate functions.
Function overloading is used when multiple functions do similar operations; templates are used when multiple functions do identical operations. Templates provide an advantage when you want to perform the same action on types that can be different.
Because the second overload will drop the top-level const
inside the const T
, it will resolve to T*
during argument deduction. The first overload is a worse match because it will resolve to S<int, char> const*
, which requires a const-qualification conversion.
You need to add const
in front of your variable s
in order for the more specialized overload to kick in:
#include <iostream>
template<class A, class B>
struct S {};
template<class A, class B>
constexpr bool isS(const S<A,B>*) {return true;}
//template<class T>
//constexpr bool isS(const T*) {return false;}
template<class T>
constexpr bool isS(const T) {return false;}
int main() {
S<int,char> const s{}; // add const here
std::cout<<isS(&s)<<std::endl;
return 0;
}
Live Example
Changing the first overload to a const S<A,B>&
, will give the correct result because there is an identity conversion instead of a qualification adjustment.
13.3.3.1.4 Reference binding [over.ics.ref]
1 When a parameter of reference type binds directly (8.5.3) to an argument expression, the implicit conversion sequence is the identity conversion, unless the argument expression has a type that is a derived class of the parameter type, in which case the implicit conversion sequence is a derived-to-base Conversion (13.3.3.1).
Note: when in doubt about such argument deduction games, it's handy to use the __PRETTY_FUNCTION__
macro which (on gcc/clang) will give you more information about the deduced types of the selected template. You can then comment out certain overloads to see how this affects the overload resolution. See this live example.
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