Resolving function template overload with pointer argument

The following code demonstrates the core of the C ++ template metaprogramming pattern that I used to determine if type T instance of a particular class pattern:

 #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 contains two overloads of the constexpr isS function template and 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 displays 0 . If both versions of isS go into the compilation phase with overloading, the output means that the compiler chooses the second overload. I tested this under GCC, Clang, and vC ++ using the online compilers here , and they all give the same result. Why is this happening?

I read Herb Sutter's article โ€œWhy not specialize function templatesโ€ several times, and it seems that both isS functions should be considered basic templates. If so, then we are talking about which one is the most specialized. Following intuition and this answer , I would expect the first isS be the most specialized, because T can correspond to each instance of S<A,B>* , and there are many possible instances of T that cannot correspond to S<A,B>* . I would like to find a paragraph in a working draft that defines this behavior, but I'm not quite sure which stage of compilation is causing the problem. Is this somehow related to "14.8.2.4 Subtracting template arguments during partial ordering"?

This problem is especially surprising given that the following code, in which the first isS accepts a reference to const S<A,B> , and the second accepts 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 related to the way pointers are handled.

+5
source share
2 answers

Since the second overload will drop the top level of const inside const T , it will solve T* during the output of the argument. The first overload is worse because it resolves to S<int, char> const* , which requires a const-qualification conversion.

You need to add const in front of your s variable to get a more specialized overload:

 #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 const S<A,B>& will give the correct result, because instead of adjusting the qualifications, the identifier is converted.

13.3.3.1.4 Binding Link [over.ics.ref]

1 When a parameter of a reference type is directly associated (8.5.3) with an argument expression, an implicit conversion sequence is an identity transformation, if only the argument expression has a type that is a derived class parameter type, in which case the implicit conversion sequence is a base-based conversion data (13.3.3.1).

Note If you have doubts about such games with argument subtraction, it is convenient to use the __PRETTY_FUNCTION__ macro, which (in gcc / clang) will give you more information about the output types of the selected template. Then you can comment on some overloads to see how this affects the resolution overload. See a live example .

+6
source

The second version does not give the answer you expect, because the first version of isS requires an implicit conversion, while the second does not.

 template<class A, class B> constexpr bool isS(const S<A,B>*); template<class T> constexpr bool isS(const T); S<int,char> s; isS(&s); 

Note that &s is of type S<int,char>* . The first isS looking for const S<int,char>* , so the pointer needs to be converted. The second isS is a direct match.


If you often need this template, you can generalize it like this:

 template<template<typename...> class TT, typename T> struct is_specialization_of : std::false_type {}; template<template<typename...> class TT, typename... Ts> struct is_specialization_of<TT, TT<Ts...>> : std::true_type {}; 

Then you check if the type is a specialization of S as follows:

 is_specialization_of<S, decltype(s)>::value 
+3
source

Source: https://habr.com/ru/post/1246551/


All Articles