为什么类型特征不适用于命名空间范围内的类型?

Why do type traits not work with types in namespace scope?

本文关键字:类型 范围内 命名空间 适用于 特征 不适用 为什么      更新时间:2023-10-16

我正在为我的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,静态断言失败。

为什么会有区别?

在查找reg((T*))以查找所引用的reg时,会检查两组位置。 第一个是声明模板的位置(int reg(...)可见的位置),第二个是 ADL 在模板首次使用新类型实例化的位置。

ns::type2*上的 ADL(参数相关查找)不检查全局命名空间。 它检查与该类型关联的命名空间,即在本例中为ns。 ADL 不会检查"围绕"或"高于"关联命名空间的命名空间。

::type1的 ADL 会检查全局命名空间。

模板不是宏。 它们的行为就像您在实例化生成的代码时复制粘贴了它一样。MSVC 过去将模板视为宏,但它们越来越符合标准。 他们为其合规性工作指定的名称是"两阶段名称查找",如果您想跟踪它在特定版本中中断的原因。

解决方法是将reg移动到ns::type2的命名空间中,或者确保您在其中定义reg的命名空间与要reg的参数相关联(例如使用标记模板而不是指针),或者在定义其在decltype中的用法之前定义reg。 或者更花哨的东西;没有潜在的问题描述,我无法猜测。

TLDR 该机制被称为两阶段查找,其规则是晦涩难懂的。经验法则是始终在与它用于避免恶作剧的类型相同的命名空间中声明函数。

当存在依赖名称时,会发生 2 阶段查找,此时名称查找将延迟到实例化点。如果名称为非限定,则查找的结果是在定义点的非限定查找和实例化点的参数相关查找的并集。

这到底意味着什么?

依赖名称

如果名称(例如函数名称)的含义取决于模板参数,则该名称(例如函数名称)是相关的。在您的情况下,reg取决于T,因为参数类型T*取决于T

实例化点

模板别名不是类型,它们表示整个类型族。当您为其提供参数时,该类型被称为从模板实例。实例化点是程序中模板别名首次与实际参数一起使用的位置。

非限定名称

如果名称之前没有范围解析运算符,则称其为不合格,例如reg是不合格的。

不合格的查找

每当一个名称出现在程序中时,都必须找到它的声明,这称为名称查找。非限定查找从名称出现的作用域中查找名称,并按顺序向外搜索。

依赖于参数的查找

也称为 ADL,这是另一个查找规则,当要查找的函数名称不限定并且函数的参数之一是用户定义的类型时,它适用。它在类型的关联命名空间中查找名称。关联的命名空间包括定义类型的命名空间等。

总之,由于is_known是在以下reg重载之前定义的,非限定查找只能找到reg(...)。由于reg(ns::type2*)不在ns::type2的相关命名空间中,因此ADL也找不到它。