带有指针参数的函数模板重载决策
Function template overload resolution with a pointer argument
以下代码演示了我一直用来确定类型 T
是否是特定 class 模板的实例化的 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" 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,我希望第一个 isS
是最专业的,因为 T
可以匹配 S<A,B>*
的每个实例,并且T
有许多可能的实例无法匹配 S<A,B>*
。我想在工作草案中找到定义此行为的段落,但我不完全确定哪个编译阶段导致了问题。这与 "14.8.2.4 在部分排序期间推导模板参数"?
有关吗
考虑到以下代码(其中第一个 isS
引用 const S<A,B>
而第二个引用 const T
,输出预期值
这个问题特别令人惊讶=16=]:
#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;
}
所以问题似乎与指针的处理方式有关。
因为第二次重载会将顶层 const
放到 const T
中,它会在参数推导期间解析为 T*
。第一个重载是一个较差的匹配,因为它将解析为 S<int, char> const*
,这需要一个 const 限定转换。
您需要在变量 s
前添加 const
以便更专业的重载启动:
#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;
}
将第一个重载更改为 const S<A,B>&
,将给出正确的结果,因为存在身份转换而不是资格调整。
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).
注意:当对这种论证推导游戏有疑问时,使用 __PRETTY_FUNCTION__
宏会很方便,它(在 gcc/clang 上)会给你更多有关所选模板的推导类型的信息。然后您可以注释掉某些重载以查看这如何影响重载决议。看到这个 live example.
您的第二个版本没有给出您期望的答案,因为 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
以下代码演示了我一直用来确定类型 T
是否是特定 class 模板的实例化的 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" 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,我希望第一个 isS
是最专业的,因为 T
可以匹配 S<A,B>*
的每个实例,并且T
有许多可能的实例无法匹配 S<A,B>*
。我想在工作草案中找到定义此行为的段落,但我不完全确定哪个编译阶段导致了问题。这与 "14.8.2.4 在部分排序期间推导模板参数"?
考虑到以下代码(其中第一个 isS
引用 const S<A,B>
而第二个引用 const T
,输出预期值
这个问题特别令人惊讶=16=]:
#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;
}
所以问题似乎与指针的处理方式有关。
因为第二次重载会将顶层 const
放到 const T
中,它会在参数推导期间解析为 T*
。第一个重载是一个较差的匹配,因为它将解析为 S<int, char> const*
,这需要一个 const 限定转换。
您需要在变量 s
前添加 const
以便更专业的重载启动:
#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;
}
将第一个重载更改为 const S<A,B>&
,将给出正确的结果,因为存在身份转换而不是资格调整。
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).
注意:当对这种论证推导游戏有疑问时,使用 __PRETTY_FUNCTION__
宏会很方便,它(在 gcc/clang 上)会给你更多有关所选模板的推导类型的信息。然后您可以注释掉某些重载以查看这如何影响重载决议。看到这个 live example.
您的第二个版本没有给出您期望的答案,因为 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