围绕函数调用解析的混淆
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-directives 像 using 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
是不可能的(因为部分函数模板专门化是被禁止的),并且您不能将自定义重载添加到命名空间 std
。 std::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
。
本题灵感来自
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-directives 像 using 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
是不可能的(因为部分函数模板专门化是被禁止的),并且您不能将自定义重载添加到命名空间 std
。 std::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
。