为什么两阶段查找无法选择"交换"的重载版本?

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

本文关键字:交换 选择 重载 版本 段查找 查找 为什么      更新时间:2023-10-16

我正在研究有关最佳实践的最佳问题,以实现用户定义的类型的最佳实践。(我的问题最初是出于讨论在名称空间std中添加类型的非法性的讨论。)

我不会在此处的上述答案中重新打印代码片段。

相反,我想了解答案。

我上面已链接的答案,在第一个代码段下方,涉及namespace std 中的Overloading swap(而不是在该名称空间中使用专业化):

如果您的编译器打印出不同的东西,那不是 正确实现模板的"两相查找"。

答案然后继续指出,namespace std 中专门使用swap(而不是 Overloading IT)产生不同的结果 perifeiratization )。

但是,答案以其他情况进行:专门为用户定义的 template class - - 在这种情况下,再次无法实现所需结果。

不幸的是,答案简单 state 事实;它不会解释为什么

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

  • namespace std中的swap过载CC_7用于用户定义的非网板类(如链接答案的第一个代码段)

  • swap0中专门针对用户定义的模板类(如链接答案的最终代码段)

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

(这将阐明两相查找的性质,以及实现用户定义的swap的最佳实践的原因。)

preamble具有大量标准

示例中对swap()的调用需要一个因名称,因为其参数begin[0]begin[1]取决于周围algorithm()功能模板的模板参数T。标准在以下标准中定义了此类相关名称的两相名称查找:

14.6.4.2候选功能[temp.dep.candidate]

1的函数调用,其中帖子固定表达是一个因名称, 使用通常的查找规则(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)中的后缀表表达为 an 不合格的ID ,在通常的情况下未考虑其他名称空间 可以搜索不合格的查找(3.4.1),在这些名称空间中, 命名空间范围的朋友函数或功能模板声明 (11.3)在其他情况下找不到。这些修改对 搜索取决于参数的类型(以及模板模板 参数,模板参数的名称空间)。

将标准应用于示例

第一个示例调用exp::swap()。这不是因名称,也不需要两相名称查找。由于互换的调用是合格的,因此发生了普通查找,仅找到通用swap(T&, T&)功能模板。

第二个示例(@howardhinnant称之为"现代解决方案")称swap()为CC_21,并且在与class A寿命的同一命名空间中也具有过载swap(A&, A&)(在这种情况下为全局名称空间)。因为互换的呼吁是不合格的,所以普通查找和ADL都在定义点发生(再次找到通用的swap(T&, T&)),但在实例化点发生了另一个ADL(即在main()中调用exp::algorithm()),这是这样拾取swap(A&, A&),这是在超载分辨率期间更好的匹配。

到目前为止一切都很好。现在为Encore:第三个示例调用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实例 - IE,std::vector<int>是一个实例,而std::vector<T>是整个template类)。似乎是专业化实际上是一个过载。因此,第一段适用。

实现用户定义的swap的最佳实践是在与templateclass相同的名称空间中引入swap功能或过载。

然后,如果在正确的上下文(using std::swap; swap(a,b);)中调用swap,这就是std库中的称呼,ADL将启动,并且您的过载将被发现。

另一个选项是针对您的特定类型进行stdswap的完整专业化。对于template类是不可能的(或不切实际的),因为您需要专门针对存在的template类的每个实例。对于其他类别,它是脆弱的,因为专业化仅适用于该特定类型:子类也必须在std中进行重新定位。

通常,功能的专业化非常脆弱,最好引入超r效力。由于您无法将覆盖物引入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,第二个是将其包含在Inline friend中。"外部运算符"的直列friend是一种常见模式,基本上意味着您只能通过ADL找到swap,但是在此特定上下文中,它不会添加太多。