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.
const Tparameter is equivalent to aTparameter (so you'll get an exact match), and you need a qualification conversion fromS<int, char>*toS<int, char> const*. Try usingS<int, char> const s;instead in yourmain. -- The more specialized test happens very late in overload resolution, if other tests could not decide between the overloads. Here, we can select the second one earlier because it has a better rank (exact match vs qualification adjustment).isSin the code snippet at the bottom, involving references? Doesn't the compiler need to do a qualification conversion fromS<int, char>&toconst S<int, char>&in this case?T*toconst T*is a qualification adjustment conversion,Ttoconst T&is an identity conversion as the reference binds directly to the argument.