围绕函数调用解析的混淆

Confusion around function call resolution

本题灵感来自。考虑代码:

namespace ns {
  template <typename T>
  void swap(T& a, T& b) {
    using namespace std;
    swap(a, b);
  }
}

在使用 GCC 进行一些测试后,我发现 swap(a, b); 解析为
1) std::swap 如果 T 已超载 std::swap(例如,标准容器类型)
2) ns::swap否则,导致无限递归。
所以,编译器似乎会首先尝试在命名空间 ns 中找到匹配项。如果找到匹配项,则搜索结束。但是当ADL进来的时候就不是这样了,这样的话,无论如何都会找到std::swap。解析过程好像比较复杂

我想知道在上述上下文中解析函数调用 swap(a, b) 过程中发生的事情的详细信息。参考标准将不胜感激。

OP 中的代码等同于

using std::swap; // only for name lookup inside ns::swap

namespace ns {
  template <typename T>
  void swap(T& a, T& b) {
    swap(a, b);
  }
}

为什么?因为 using-directivesusing namespace std; 有一个非常特殊的行为 C++14 [namespace.udir]p2:

A using-directive specifies that the names in the nominated namespace can be used in the scope in which the using-directive appears after the using-directive. During unqualified name lookup, the names appear as if they were declared in the nearest enclosing namespace which contains both the using-directive and the nominated namespace.

同时包含命名空间 std 和函数块作用域 ns::swap 的最近封闭命名空间是全局命名空间。

Using-declarations 另一方面 using std::swap; 确实将名称引入它们出现的范围,而不是某些封闭范围。


函数调用表达式如swap(a, b)的查找称为非限定查找。标识符 swap 没有用任何名称空间或 class 名称限定,这与 ns::swap 不同,后者已通过 ns:: 限定。函数潜在名称的非限定查找由两部分组成:纯非限定查找和参数相关查找。

纯非限定查找在最近的包含该名称的封闭范围内停止。在 OP 的示例中,如上所示的等效转换所示,包含名称 swap 声明的最近范围是命名空间 ns。不会搜索全局范围,std::swap不会通过纯非限定查找找到。

依赖于参数的查找搜索与参数类型关联的所有范围(此处:仅命名空间和 classes)。对于 class 类型,在其中声明 class 的名称空间是关联范围。 C++ 标准库的类型,例如 std::vector<int> 与命名空间 std 相关联,因此 std::swap 可以通过表达式 swap(a, b) 的参数相关查找找到 T ] 是 C++ 标准库类型。同样,您自己的 class 类型允许在声明它们的命名空间中找到 swap 函数:

namespace N2 {
    class MyClass {};
    void swap(MyClass&, MyClass&);
}

因此,如果依赖于参数的查找没有找到比纯非限定查找更好的匹配,您最终将递归调用 ns::swap


调用 swap 不合格的想法,即 swap(a, b) 而不是 std::swap(a, b) 是假设通过参数相关查找找到的函数比 [=23 更专业=].为您自己的 class 模板类型专门化一个函数模板,例如 std::swap 是不可能的(因为部分函数模板专门化是被禁止的),并且您不能将自定义重载添加到命名空间 stdstd::swap 的通用版本通常实现如下:

template<typename T>
void swap(T& a, T& b)
{
    T tmp( move(a) );
    a = move(b);
    b = move(tmp);
}

这需要一个移动构造加上两个移动分配,这甚至可能会退回到副本。因此,您可以在与这些类​​型关联的命名空间中为您自己的类型提供 专门的交换函数 。您的专用版本可以使用您自己的类型的某些属性或私有访问权限。

标准中最重要的部分是 7.3.4/2(引用 C++14 n4140,强调我的):

A using-directive specifies that the names in the nominated namespace can be used in the scope in which the using-directive appears after the using-directive. During unqualified name lookup (3.4.1), the names appear as if they were declared in the nearest enclosing namespace which contains both the using-directive and the nominated namespace.

using-directive 位于 :: ns 中的函数内部,并指定 :: std。这意味着,出于非限定名称查找的目的,此 using-directive 的效果是 ::std 中的名称的行为就好像它们是在 :: 中声明的一样。特别是,不像他们在::ns.

因为非限定名称查找是在 ::ns 中的一个函数内部开始的,它会在查找 :: 之前先搜索 ::ns。它找到 ::ns::swap,所以它在那里结束,而不检查 ::,它会找到 using 指令引入的 ::std::swap