带有指针参数

时间:2016-04-06 12:20:05

标签: c++ pointers overload-resolution function-templates 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;
}

它具有constexpr函数模板isS的两个重载,并按预期输出1。如果我从第二个isS中删除指针,即将其替换为

template<class T>
constexpr bool isS(const T) {return false;}

程序意外输出0。如果isS的两个版本都通过编译的重载决策阶段,则输出意味着编译器正在选择第二个重载。我已经使用在线编译器here在GCC,Clang和vc ++下进行了测试,它们都产生了相同的结果。为什么会这样?

我已多次阅读Herb Sutter的"Why Not Specialize Function Templates"文章,似乎两个isS函数都应被视为基本模板。如果是这样,那么问题是哪一个是最专业的。通过直觉和this answer,我希望第一个isS是最专业的,因为T可以匹配S<A,B>*的每个实例,并且有许多可能的实例化T无法匹配的S<A,B>*。我想在工作草案中找到定义此行为的段落,但我不完全确定编译的哪个阶段导致了问题。是否与&#34; 14.8.2.4在部分排序期间扣除模板参数&#34;

有关

考虑到以下代码,其中第一个isS引用const S<A,B>而第二个const T引用1,此问题特别令人惊讶,输出预期值{{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;
}

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

2 个答案:

答案 0 :(得分: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;
}

Live Example

将第一次重载更改为const S<A,B>&,会得到正确的结果,因为存在身份转换而不是资格调整。

  

13.3.3.1.4参考绑定[over.ics.ref]

     

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

注意:如果对此类参数演绎游戏有疑问,使用__PRETTY_FUNCTION__宏(在gcc / clang上)将为您提供有关推导类型的更多信息是很方便的。选定的模板。然后,您可以注释掉某些重载,以查看它如何影响重载决策。请参阅此live example

答案 1 :(得分:3)

您的第二个版本没有给出您期望的答案,因为isS的第一个版本需要隐式转换,而第二个版本不需要。

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);

请注意,&s的类型为S<int,char>*。第一个isS正在寻找const S<int,char>*,因此指针需要转换。第二个isS是直接匹配。

如果你发现自己经常需要这种模式,你可以概括一下,如下:

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 {};

然后你检查一个类型是S的特化是这样的:

is_specialization_of<S, decltype(s)>::value