繁体   English   中英

为什么两阶段查找无法选择'swap'的重载版本?

[英]Why will two-phase lookup fail to choose overloaded version of 'swap'?

我正在研究这个关于为用户定义的类型实现swap功能最佳实践微妙问题的 这个引人入胜的答案 (我的问题最初的动机是讨论将类型添加到命名空间std的非法性 。)

我不会在这里重新打印上面链接的答案中的代码片段。

相反,我想了解答案。

我在上面链接的答案在第一个代码片段下面说明了namespace std重载swap (而不是在该命名空间中专门化它):

如果您的编译器打印出不同的东西,那么它就不能正确地为模板实现“两阶段查找”。

答案然后继续指出, 专业swapnamespace std (相对于超载产生不同的结果 (在专业化的情况下所希望的结果)。

但是,答案继续进行另外一种情况: 为用户定义的模板类专门交换 - 在这种情况下,再次无法实现所需的结果。

不幸的是,答案只是陈述事实; 它没有解释原因

有人可以详细说明这个答案,并在该答案中提供的两个特定代码片段中描述查找过程:

  • namespace std为用户定义的非模板类重载swap (如链接答案的第一个代码片段)

  • namespace std专门为用户定义的模板类swap (如链接答案的最终代码片段)

在这两种情况下,都会调用通用的std::swap ,而不是用户定义的swap 为什么?

(这将阐明两阶段查找的本质,以及实现用户定义swap最佳实践的原因;谢谢。)

有很多Standardese的序言

示例中对swap()的调用需要一个依赖名称,因为它的参数begin[0]begin[1]取决于周围algorithm()函数模板的模板参数T 此类依赖名称的两阶段名称查找在标准中定义如下:

14.6.4.2候选函数[temp.dep.candidate]

1对于post fi x-expression是从属名称的函数调用,使用通常的查找规则(3.4.1,3.4.2)找到候选函数,除了:

- 对于使用非标准名称查找(3.4.1)的查找部分,仅找到模板定义上下文中的函数声明。

- 对于使用关联命名空间(3.4.2)的查找部分,仅找到在模板定义上下文或模板实例化上下文中找到的函数声明。

非限定查找由。定义

3.4.1不合法的名称查找[basic.lookup.unqual]

1在3.4.1中列出的所有情况下,在每个相应类别中列出的顺序中搜索范围; 一旦找到名称的声明,名称查找就会结束 如果没有找到声明,该程序就是格式错误。

和参数依赖查找(ADL)为

3.4.2依赖于参数的名称查找[basic.lookup.argdep]

1当函数调用(5.2.2)中的postfix-expression是非限定id时 ,可以搜索在通常的非限定查找(3.4.1)期间未考虑的其他名称空间,并在这些名称空间中搜索名称空间范围的朋友函数或可以找到不可见的函数模板声明(11.3)。 对搜索的这些修改取决于参数的类型 (以及模板模板参数,模板参数的命名空间)。

将标准应用于示例

一个示例调用exp::swap() 这不是依赖名称,不需要两阶段名称查找。 因为对swap的调用是合格的,所以发生普通查找,它只查找通用swap(T&, T&)函数模板。

第二个例子 (@HowardHinnant称之为“现代解决方案”)调用swap()并且在class A所在的同一名称空间中也有一个重载swap(A&, A&) (在本例中为全局命名空间)。 因为对swap的调用是不合格的,普通查找和ADL都发生在定义点(再次只找到通用swap(T&, T&) ),但另一个ADL发生在实例化时(即exp::algorithm()main()被调用,这将获得swap(A&, A&) ,这是在重载解析期间更好的匹配。

到现在为止还挺好。 现在为安可: 第三个例子调用swap()并在namespace exp有一个特化template<> swap(A&, A&) 查找与第二个示例中的查找相同,但现在ADL不会选择模板特化,因为它不在class A的关联命名空间中。 但是,即使专门化template<> swap(A&, A&)在重载解析期间不起作用,它仍然在使用点实例化。

最后, 第四个示例调用swap()并在namespace exp内部有一个重载template<class T> swap(A<T>&, A<T>&)用于生成在全局名称空间中的template<class T> class A 查找与第三个示例中的相同,并且ADL再次没有获取重载swap(A<T>&, A<T>&)因为它不在类模板A<T>的关联命名空间中。 在这种情况下,也没有必须在使用点实例化的专门化,因此这里调用通用swap(T&, T&)

结论

即使您不允许向namespace std添加新的重载,也只允许显式特化,但由于两阶段名称查找的各种复杂性,它甚至不起作用。

对于用户定义的类型,不可能在namespace std重载swap 引入namespace std的重载(而不是特殊化)是未定义的行为(在标准下是非法的,不需要诊断)。

一般来说, template类不能专门化一个函数(而不是template实例 - 即std::vector<int>是一个实例,而std::vector<T>是整个template类) 。 似乎是专业化实际上是一种过载。 所以第一段适用。

实现用户定义swap的最佳实践是在templateclass所在的同一名称空间中引入swap函数或重载。

然后,如果在正确的上下文中调用swapusing std::swap; swap(a,b); ),这是在std库中调用它的方式,ADL将启动,并且将找到您的重载。

另一种选择是针对您的特定类型对std执行swap的完全特化。 这对于template类是不可能的(或不切实际的),因为您需要专门针对存在的template类的每个实例。 对于其他类,它很脆弱,因为专门化仅适用于特定类型:子类也必须在std重新特定。

一般来说,功能的专业化非常脆弱,你最好不要引入覆盖。 由于您无法将替代引入std ,因此可靠地找到它们的唯一位置是您自己的namespace 您自己的命名空间中的这些覆盖也优于std覆盖。

有两种方法可以将swap注入命名空间。 两者都为此目的而努力:

namespace test {
  struct A {};
  struct B {};
  void swap(A&, A&) { std::cout << "swap(A&,A&)\n"; }
  struct C {
    friend void swap(C&, C&) { std::cout << "swap(C&, C&)\n"; }
  };

  void bob() {
    using std::swap;
    test::A a, b;
    swap(a,b);
    test::B x, y;
    swap(x, y);
    C u, v;
    swap(u, v);
  }
}

void foo() {
  using std::swap;
  test::A a, b;
  swap(a,b);
  test::B x, y;
  swap(x, y);
  test::C u, v;
  swap(u, v);

  test::bob();
}
int main() {
  foo();
  return 0;
}

第一种是直接将它注入namespace ,第二种是将其作为内联friend包含在内。 “外部运算符”的内联friend是一种常见的模式,基本上意味着你只能通过ADL找到swap ,但在这个特定的上下文中并没有增加多少。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM