我正在为我的C ++序列化库设计类型注册功能。 但是我在类型特征方面遇到了一个奇怪的问题。
我将Visual Studio 2017与/ std:c ++ latest一起使用。
#include <type_traits>
int reg(...);
template<class T>
constexpr bool is_known = !std::is_same_v<decltype(reg((T*)1)), int>;
//----- for type1 in global scope ------
struct type1 {};
void reg(type1 *);
static_assert(is_known<type1>); // success
//----- for type2 in namespace scope ----
namespace ns { struct type2 { }; }
void reg(ns::type2 *);
static_assert(is_known<ns::type2>); // fail!!!!
对于类型1(在全局范围内),静态断言成功,但对于名称空间范围(类型2),静态断言失败。
为什么有区别?
答案 0 :(得分:5)
完成reg((T*))
的查找以查找哪个reg
时,检查了两组位置。第一个是声明模板的位置(在int reg(...)
可见的位置),第二个是第一次使用新类型实例化模板的点的ADL。
ADL(依赖于参数的查找)不检查全局名称空间。它检查与该类型关联的名称空间,在这种情况下,即ns::type2*
。 ADL不会检查“周围”或“上方”相关名称空间的名称空间。
ns
的ADL确实检查了全局名称空间。
模板不是宏。它们的作用不像您在实例化时复制粘贴了生成的代码。 MSVC过去将模板更像是宏,但是它们越来越符合该标准。如果您想跟踪特定版本中出现中断的原因,他们为遵守法规所做的命名就是“两阶段名称查找”。
解决方法是将::type1
移到reg
的命名空间中,或者确保在其中定义ns::type2
的命名空间与reg
的参数相关联(例如使用标签模板而不是指针),或者在reg
中定义reg
的用途之前先定义decltype
。或者更奇特的东西;没有基本的问题描述,我无法猜测。
答案 1 :(得分:5)
TLDR该机制被称为2相查找,其规则是不可思议的。经验法则是始终将函数声明与其用于避免恶作剧的类型相同的命名空间。
当存在从属名称时,会发生2阶段查找,此时名称查找将延迟到实例化点。如果名称不合格,则查找的结果是在定义时进行不合格查找的并在实例化时进行参数依赖的查找的合并。
现在这到底意味着什么?
如果名称(例如函数名称)的含义取决于模板参数,则该名称是依赖的。在您的情况下,reg
取决于T
,因为参数类型T*
取决于T
。
模板别名不是类型,它们代表整个类型的族。当您为其提供参数时,该类型被称为是 instantiated 。实例化点是程序中首先将模板别名与实际参数一起使用的位置。
如果名称之前没有作用域解析运算符,则称该名称为不合格,例如reg
不合格。
只要程序中出现名称,就必须查找其声明,这称为名称查找。不合格的查找从名称出现的范围中查找名称,然后依次向外搜索。
也称为ADL,这是另一个查找规则,它在要查找的函数名称不合格且函数的自变量之一是用户定义的类型时适用。它将在类型的关联命名空间中找到该名称。关联的名称空间包括定义类型的名称空间,等等。
最后,由于is_known
是在以下reg
的重载之前定义的,因此不合格的查找只能找到reg(...)
。由于reg(ns::type2*)
不在ns::type2
的关联命名空间中,因此ADL也找不到它。