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

时间:2014-09-19 02:41:35

标签: c++ templates gcc clang

考虑以下代码:

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
}

Demo。由于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);
}

Demo

这里发生了什么?为什么u = v;导致D<A>的隐式实例化 - 在B定义的主体中没有使用的类型 - 在第一种情况下但不是第二λ

2 个答案:

答案 0 :(得分:19)

问题的全部内容是ADL开始:

N3797 - [basic.lookup.argdep]

  

当函数调用(5.2.2)中的postfix-expression是非限定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)

即。类类型的完整性实际影响朋友声明 - &gt;因此,类类型的完整性会影响程序的语义。 这也是你的第二个样本有效的原因。

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开始(在撰写本文时,它已在几分钟前提交),此行为已更改为根本不执行operator=的ADL。


<强>参考

感谢理查德史密斯和大卫布莱基帮我解决这个问题。

答案 1 :(得分:-2)

好吧,我认为在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 - 它是正确的 - struct B没有任何构造函数=&gt; B :: p未初始化,可能包含垃圾。