为什么(不相关的)using声明可以使用Argument-Dependent Lookup协调过载歧义?

时间:2017-10-17 07:48:47

标签: c++ scope namespaces argument-dependent-lookup using-declaration

这是关于使用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位)中重现。

2 个答案:

答案 0 :(得分:2)

这里的机制有三个。

  1. 使用声明,如using std::swap是声明。它将swap的声明引入函数的声明区域。

  2. 另一方面,使用指令不会将声明引入当前声明性区域。它只允许非限定查找来处理指定命名空间中的名称,就像它们是在当前声明区域的最近的封闭命名空间中声明的那样。

  3. 较小的声明区域中的声明会隐藏较大的封闭声明区域中的声明。

  4. 关于上述内容,您设置它的方式如下:

    1. std::swapswap(Foo, Foo)内声明。
    2. myNS中的名称可供swap(Foo, Foo)使用,就好像它们是在同一名称空间中声明一样。
    3. #1中添加的声明隐藏了#2中可见的声明。
    4. ADL可以找到
    5. ::swap(尽管也被#1隐藏),但myNS::swap无法解决。由于myNS版本既隐藏了,也没有被ADL找到,因此它不会与任何内容发生冲突。
    6. 当您删除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不会为此案件提起诉讼。

  

名称查找检查范围如下所述,直到它找到至少一个任何类型的声明,此时查找停止并且不再检查其他范围。