围绕函数调用解析的混淆

Confusion around function call resolution

本文关键字:函数调用      更新时间:2023-10-16

这个问题的灵感来自这个问题。考虑代码:

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

在使用 GCC 进行一些测试后,我发现swap(a, b);解析为
1)std::swapT是否重载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 namespace std;这样的using 指令有一个非常特殊的行为C++14 [namespace.udir]p2:

using-指令指定指定命名空间中的名称 可以在using-指令出现在之后的范围内使用使用指令。在非限定名称查找期间,名称 看起来好像它们是在最近的封闭命名空间中声明的 它包含using-指令和指定的命名空间。

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

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


函数调用表达式(如swap(a, b))的查找称为非限定查找。标识符swap没有用任何命名空间或类名限定,而ns::swap则通过ns::限定。对函数潜在名称的非限定查找由两部分组成:纯非限定查找和依赖于参数的查找。

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

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

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

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


调用swap非限定(即swap(a, b)而不是std::swap(a, b))背后的想法是,通过依赖于参数的查找找到的函数被认为比std::swap更专业。不可能将函数模板(如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,强调我的):

using-指令指定指定命名空间中的名称可以在非限定名称查找 (3.4.1) 期间,名称将显示 就好像它们是在最近的封闭命名空间中声明的,该命名空间同时包含using-指令和 指定的命名空间。

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

.因为非限定名查找始于::ns函数内部,它会先搜索::ns,然后再查找::。它找到::ns::swap,所以它到此结束,而不检查::,在那里它会找到由 using-指令引入::std::swap