GCC 和 Clang 在运算符重载解析期间隐式实例化模板参数

gcc and clang implicitly instantiate template arguments during operator overload resolution

本文关键字:实例化 参数 Clang 运算符 重载 GCC      更新时间:2023-10-16

请考虑以下代码:

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 未初始化并且可能包含垃圾。