3

这是关于使用参数依赖查找 (ADL) 的函数重载的问题后续行动。我想检查我在这些情况下对规则的理解,所以我写了一些测试代码。

首先,当然,std 中没有 HasPtr 类的交换,所以我编写了自己的命名空间,其中包含一个 HasPtr 版本的交换,除了已经在全局范围中定义的交换。using 声明按我的预期工作——产生了一个模棱两可的错误,因为已经定义了一个 HasPtr 版本的交换,如 "C++ Primer", 5ed 中所做的那样。

然后我想看看如果我将 using 声明更改为 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);
}

using std::swap;第 27 行 ( )发生了奇怪的事情。如果我将其注释掉,与已在全局范围中定义的签名完全相同的名称 myNS::swap 将被提升到全局范围,从而导致重载歧义错误,正如我所料。

但是,如果我不注释第 27 行并编译,则不会报告歧义错误。并且程序执行最初在全局范围内定义的 ::swap,就好像 using 指令using namespace myNS;没有解除 myNS::swap 一样,因此它不会添加到重载候选集中。我只是无法理解这种现象。为什么来自不相关命名空间的 using 声明(std 当然不包含交换的 HasPtr 版本)可以在 ADL 下协调重载歧义?为什么选择执行的是原来的 ::swap,而不是它在 myNS 中的竞争对手?第 27 行是否对重载过程有任何副作用(例如,从提升的命名空间中抑制名称,以便原始名称具有更高的优先级)?谢谢您的回答。

该问题可以在 Windows 7 上的 Visual Studio 2015 Update 3 和 ubuntu 14.04 上的 GCC 4.8.4(均为 64 位)中重现。

4

2 回答 2

2

这里起作用的机制是三方面的。

  1. 一个 using声明,就像using std::swap是一个声明。它swap在函数的声明区域中引入了声明。

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

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

关于上述内容,您的设置方式是这样的:

  1. std::swap在里面声明swap(Foo, Foo)
  2. 内部的名称myNS可供swap(Foo, Foo).
  3. #1 中添加的声明隐藏了#2 中可见的声明。
  4. ::swap可以通过 ADL 找到(尽管也被 #1 隐藏),但myNS::swap不能。由于该myNS版本是隐藏的,ADL 找不到,所以它不会与任何东西冲突。

当您删除 的声明时std::swap,现在您已经myNS::swap可见。ADL 也发现::swap了,给你两个重载。它们都是有效的重载,并产生明显的歧义。

于 2017-10-17T08:07:42.057 回答
1

注意using-directive和 using-declaration 有不同的效果:

(强调我的)

using-directive:从 using-directive 之后的任何名称的非限定名称查找的角度来看,直到它出现的范围结束,来自 ns_name 的每个名称都是可见的,就好像它是在最近的封闭命名空间中声明的一样包含 using-directive 和 ns_name

这意味着对于using namespace myNS;,名称myNS::swap是可见的,就好像它是在全局范围内声明的一样。如果using std::swap;被注释,那么 2 swaps 将在全局范围内找到,然后导致歧义。

如果using std::swap;未注释,则将在函数范围内找到swapfrom命名空间,然后停止名称查找,不会检查进一步的全局范围。请注意,全局可以由 ADL 找到,加上它们将在重载决议中被考虑,并且被选择然后没有歧义。不会在这种情况下踢。std::swapstd::swap::swapmyNS::swap

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

于 2017-10-17T08:05:01.927 回答