围绕功能调用解决方案的混乱

Lin*_*gxi 7 c++ function-calls language-lawyer overload-resolution argument-dependent-lookup

这个问题的灵感来自于这个问题.考虑一下代码:

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

在使用GCC进行一些测试后,我发现swap(a, b);解析为
1)std::swap如果T已经过载std::swap(例如,标准容器类型)
2)ns::swap否则,导致无限递归.
因此,似乎编译器将首先尝试在命名空间中找到匹配项ns.如果找到匹配项,则搜索结束.但是当ADL进入时并非如此,在这种情况下,std::swap无论如何都会找到.解决过程似乎很复杂.

我想知道swap(a, b)在上面的上下文中解析函数调用过程中发生了什么的细节.可以参考该标准.

dyp*_*dyp 6

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);
  }
}
Run Code Online (Sandbox Code Playgroud)

为什么?因为using指令using namespace std;具有非常独特的行为C++ 14 [namespace.udir] P2:

一个using指令规定,在被提名的命名空间中的名字可以在该范围内使用using指令后出现using指令.在非限定名称查找期间,名称看起来好像是在最近的封闭命名空间中声明的,该命名空间同时包含using-directive和指定的命名空间.

包含命名空间std和块功能范围的最近的封闭命名空间ns::swap是全局命名空间.

using std::swap;另一方面,诸如声明之的声明确实将名称引入它们出现的范围,而不是在某些封闭范围中.


查找函数调用表达式,例如swap(a, b)称为非限定查找.所述标识符swap没有被限定与任何命名空间或类名,而不是ns::swap,这已通过已经合格ns::.对函数的潜在名称进行非限定查找包括两部分:纯不合格查找和依赖于参数的查找.

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

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

namespace N2 {
    class MyClass {};
    void swap(MyClass&, MyClass&);
}
Run Code Online (Sandbox Code Playgroud)

因此,如果依赖于参数的查找找不到比纯非无限制查找更好的匹配,那么最终将以ns::swap递归方式调用.


调用swap不合格的背后的想法,即swap(a, b)代替std::swap(a, b)通过依赖于参数的查找找到的函数被假定为更专业std::swap.专门std::swap为您自己的类模板类型的函数模板是不可能的(因为禁止部分函数模板特化),并且您可能不会向命名空间添加自定义重载std.通用版本的std::swap实现通常如下:

template<typename T>
void swap(T& a, T& b)
{
    T tmp( move(a) );
    a = move(b);
    b = move(tmp);
}
Run Code Online (Sandbox Code Playgroud)

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