这是关于使用Argument-Dependent Lookup(ADL)的函数重载问题here的后续内容。我想在这些情况下检查我对规则的理解,所以我写了一些测试代码。
首先,当然,std中没有HasPtr类的交换,所以我写了一个我自己的命名空间,除了已经在全局范围内定义的版本之外,它还包含一个HasPtr版本的swap。 using声明的工作方式符合我的预期 - 产生歧义的错误,因为已经定义了一个HasPtr版本的swap,就像在" C ++ Primer",5ed中一样。
然后我想看看如果我将使用声明更改为using指令将会发生什么。该书说,在实际调用函数之前,编译器将保持静默。我想验证一下,所以代码如下:
#include <string>
class HasPtr {
public:
friend void swap(HasPtr&, HasPtr&);
std::string *ps;
};
void swap(HasPtr &lhs, HasPtr &rhs) {
swap(lhs.ps, rhs.ps); // swap the pointers, not the string data
}
namespace myNS {
void swap(HasPtr &lhs, HasPtr &rhs) {
std::string s = "in my name space";
swap(lhs.ps, rhs.ps); // swap the pointers, not the string data
}
}
class Foo {
friend void swap(Foo &lhs, Foo &rhs);
HasPtr h;
};
void swap(Foo &lhs, Foo &rhs) {
using std::swap; //<- commenting this line will cause error
using namespace myNS;
swap(lhs.h, rhs.h);
}
int main() {
Foo f1, f2;
swap(f1, f2);
}
奇怪的事情发生在第27行(using std::swap;
)。如果我将它注释掉,名称myNS :: swap与全局范围内已经定义的名称完全相同的签名被提升到全局范围,从而导致错误超载歧义,正如我所料。
但是,如果我不对第27行进行评论并进行编译,则不会报告歧义错误。并且程序执行最初在全局范围内定义的:: swap,就好像using指令using namespace myNS;
不会解除myNS :: swap,因此它不会被添加到候选集中以进行重载。我只是无法理解这种现象。为什么来自不相关命名空间的使用声明(std当然不包含掉的HasPtr版本)可以协调ADL下的过载歧义?为什么选择执行原来的:: swap,而不是myNS中的竞争对手?第27行是否对重载过程有任何副作用(比如,从提升的命名空间中抑制名称,以便原始名称具有更高的优先级)?谢谢你的回答。
问题可以在Windows 7上的Visual Studio 2015 Update 3和ubuntu 14.04上的GCC 4.8.4(64位)中重现。
答案 0 :(得分:2)
这里的机制有三个。
使用声明,如using std::swap
,是声明。它将swap
的声明引入函数的声明区域。
另一方面,使用指令不会将声明引入当前声明性区域。它只允许非限定查找来处理指定命名空间中的名称,就像它们是在当前声明区域的最近的封闭命名空间中声明的那样。
较小的声明区域中的声明会隐藏较大的封闭声明区域中的声明。
关于上述内容,您设置它的方式如下:
std::swap
在swap(Foo, Foo)
内声明。myNS
中的名称可供swap(Foo, Foo)
使用,就好像它们是在同一名称空间中声明一样。::swap
(尽管也被#1隐藏),但myNS::swap
无法解决。由于myNS
版本既隐藏了,也没有被ADL找到,因此它不会与任何内容发生冲突。当您删除std::swap
的声明后,现在可以看到myNS::swap
。 ADL也会找到::swap
,给你两个重载。它们都是有效的重载,并且产生明显的模糊性。
答案 1 :(得分:1)
请注意using-directive和using-declaration有不同的效果:
(强调我的)
using-directive:从使用指令之后的任何名称的非限定名称查找的角度来看,直到它出现的作用域的结尾,ns_name中的每个名称都可见,就像它被声明一样在最近的封闭命名空间中,它包含using-directive和ns_name 。
这意味着对于using namespace myNS;
,名称myNS::swap
可见,就像它在全局范围内声明一样。如果using std::swap;
被评论,那么将在全局范围内找到2 swap
,然后导致歧义。
如果取消注释using std::swap;
,则会在函数范围找到swap
std
命名空间,然后name lookup停止,进一步的全局范围将不会检查。请注意,ADL可以找到全局::swap
,添加std::swap
会在重载决策中考虑它们,并选择::swap
,然后不会产生歧义。 myNS::swap
不会为此案件提起诉讼。
名称查找检查范围如下所述,直到它找到至少一个任何类型的声明,此时查找停止并且不再检查其他范围。