具有指针参数的函数模板重载决策

Ose*_*Ose 5 c++ pointers function-templates overload-resolution argument-deduction

下面的代码演示了我用来确定某个类型是否T是特定类模板的实例化的C++模板元编程模式的核心:

#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;
}
Run Code Online (Sandbox Code Playgroud)

它具有两个constexpr功能模板的重载,并按预期isS输出1.如果我从第二个删除指针isS,即替换它

template<class T>
constexpr bool isS(const T) {return false;}
Run Code Online (Sandbox Code Playgroud)

该程序意外输出0.如果两个版本isS都通过编译的重载决策阶段,那么输出意味着编译器正在选择第二个重载.我已经在海湾合作委员会,并锵VC++使用在线编译器测试了这里,他们都产生相同的结果.为什么会这样?

我已多次阅读Herb Sutter的"为什么不专攻功能模板"一文,似乎这两个isS功能都应该被认为是基本模板.如果是这样,那么问题是哪一个是最专业的.通过直觉和这个答案,我希望第一个isS是最专业的,因为T可以匹配每个实例化S<A,B>*,并且有许多可能的实例化T无法匹配S<A,B>*.我想在工作草案中找到定义此行为的段落,但我不完全确定编译的哪个阶段会导致问题.是否与"14.8.2.4在部分排序期间推导模板参数"有关

这个问题特别令人惊讶,因为下面的代码,其中第一个isS接受引用const S<A,B>而第二个接受a const T,输出预期值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;
}
Run Code Online (Sandbox Code Playgroud)

所以问题似乎与如何处理指针有关.

Tem*_*Rex 6

因为第二个重载会降低顶层const内部const T,它将T*在参数推断期间解析.第一个重载是一个更糟糕的匹配,因为它将解析为S<int, char> const*,这需要一个const限定转换.

您需要const在变量前添加,s以便更专业的重载启动:

#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;
}
Run Code Online (Sandbox Code Playgroud)

实例

将第一个重载更改为a const S<A,B>&将得到正确的结果,因为存在标识转换而不是资格调整.

13.3.3.1.4引用绑定[over.ics.ref]

1当引用类型的参数直接(8.5.3)绑定到参数表达式时,隐式转换序列是标识转换,除非参数表达式的类型是参数类型的派生类,在这种情况下隐式转换转换序列是派生到基础的转换(13.3.3.1).

注意:当对这种参数演绎游戏有疑问时,使用__PRETTY_FUNCTION__宏(gcc/clang)将为您提供有关所选模板的推导类型的更多信息非常方便.然后,您可以注释掉某些重载,以查看它如何影响重载决策.看到这个实例.