GCC 和 Clang 在运算符重载解析期间隐式实例化模板参数
gcc and clang implicitly instantiate template arguments during operator overload resolution
请考虑以下代码:
struct A; // incomplete type
template<class T>
struct D { T d; };
template <class T>
struct B { int * p = nullptr; };
int main() {
B<D<A>> u, v;
u = v; // doesn't compile; complain that D<A>::d has incomplete type
u.operator=(v); // compiles
}
演示。由于u.operator=(v)
可以编译但u = v;
不编译,因此在后一个表达式的重载解析期间的某个地方,编译器必须隐式实例化D<A>
- 但我不明白为什么需要这种实例化。
为了使事情更有趣,此代码编译:
struct A; // incomplete type
template<class T>
struct D; // undefined
template <class T>
struct B { int * p = nullptr; };
int main() {
B<D<A>> u, v;
u = v;
u.operator=(v);
}
演示。
这是怎么回事?为什么u = v;
会导致D<A>
的隐式实例化 - 一种在B
定义正文中没有使用的类型 - 在第一种情况下而不是第二种情况下?
问题的全部意义在于 ADL 开始:
N3797 - [basic.lookup.argdep]
当函数调用 (5.2.2( 中的后缀表达式是非限定 id 时,不考虑其他命名空间 在通常的非限定查找(3.4.1(期间可以搜索,并且在这些命名空间中,命名空间范围 可能会找到不可见的友元函数或函数模板声明 (11.3(。
以后:
对于函数调用中的每个参数类型 T,都有一组零个或多个关联的命名空间和一个 要考虑的零个或多个关联类的集合。[...]的集合 命名空间和类按以下方式确定:
- 如果 T 是类类型 [..],则其关联的类是:... 此外,如果 T 是类模板专用化,则其关联的命名空间和类还包括: 与 为模板类型参数提供的模板参数的类型
D<A>
是一个关联的类,因此在列表中等待轮到它。
现在是有趣的部分 [temp.inst]/1
除非类模板专用化已显式实例化 (14.7.2( 或显式专用化 (14.7.3(, 类模板专用化是隐式实例化的 [...]当类类型的完整性影响程序的语义时
人们可能会认为类型D<A>
的完整性根本不影响该程序的语义,但是[basic.lookup.argdep]/4说:
考虑关联命名空间时,查找与将关联命名空间用作限定符时执行的查找相同 (3.4.3.2( 除了:
[...] 在关联类中声明的任何命名空间范围的友元函数或友元函数模板在各自的中可见 命名空间,即使它们在普通查找期间不可见 (11.3(
即类类型的完整性实际上会影响 friends 声明 ->类类型的完整性因此会影响程序的语义。这也是您的第二个示例起作用的原因。
TL;DR D<A>
被实例化。
最后一个有趣的观点是关于为什么ADL首先开始
u = v; // Triggers ADL
u.operator=(v); // Doesn't trigger ADL
§13.3.1.2/2 规定不能有非成员operator=
(内置的除外(。将它连接到 [over.match.oper]/2:
非成员候选项集是在上下文中对operator@进行非限定查找的结果 根据非限定函数调用中名称查找的常规规则的表达式 (3.4.2( 除了忽略所有成员函数。
逻辑结论是:如果表 11 中没有非成员窗体,则执行 ADL 查找毫无意义。但是 [temp.inst]p7 放宽了这一点:
如果重载解析过程可以在不实例化类模板定义的情况下确定要调用的正确函数,则不指定该实例化是否实际发生。
这就是Clang首先触发整个ADL -> implicit instantiation
过程的原因。
从 r218330 开始(在撰写本文时,它已在几分钟前提交(,此行为已更改为根本不执行 ADL operator=
。
引用
- 转 r218330
- 叮当源/塞马模块
- CFE-开发
- N3797
感谢Richard Smith和David Blaikie帮助我解决这个问题。
好吧,我认为在Visual Studio 2013中,代码应该看起来像(没有= nullptr(:
struct A; // incomplete type
template<class T>
struct D { T d; };
template <class T>
struct B { int * p; };
int void_main() {
B<D<A>> u, v;
u = v; // compiles
u.operator=(v); // compiles
return 0;
}
在这种情况下,它应该可以很好地编译,因为不完整的类型可用于特定的模板类专用化用途。
至于运行时错误 - 变量 v 在没有初始化的情况下使用 - 这是正确的 - 结构 B 没有任何构造函数 => B::p 未初始化并且可能包含垃圾。
- 在 c++ 中的模板实例化中使用带有构造函数的类作为类型参数
- 如果模板参数是另一个模板的实例化,则键入特征测试
- 如何在模板函数中实例化其长度使用模板参数的数组
- 函数在可变参数模板的实例化期间不可见
- 实例化模板时,我是否必须显式显示参数包中的类型?
- 从模板参数包实例化的访问类实现
- C++ 可变参数模板实例化深度超过最大值 900
- 与参数匹配的友元模板函数实例化
- 有没有办法根据命令行参数定义数组大小?运行时与编译时实例化?
- 在编译时检查未实例化的类模板是否继承自其第一个模板参数
- 实例化模板参数的参数包
- SFINAE 和模板函数实例化:为什么在启用了 SFINAE 类型的函数参数中使用模板参数时无法推断模板参数?
- 非类型引用参数可以在运行时修改,这是否意味着模板可以在运行时实例化?
- 无法实例化抽象类,但类不是抽象/派生方法的参数
- 有没有办法将一对元组剥离为可变参数模板类型或实例化具有可变参数类型的东西?
- unique_ptr 使用尚未定义的参数进行实例化不会导致错误
- 根据运行时参数避免模板实例化的代码重复
- 编译时检查是否有两个具有相同模板参数的模板实例化
- 如何编写模板重载函数,并在模板参数不允许实例化某个类时触发回退
- 在不使用"new"的情况下实例化参数参数中的对象