C++ `using namespace` 指令使全局范围运算符消失?

C++ `using namespace` directive makes global-scope operator disappear?

由于我不明白的原因,以下 C++ 代码无法在 VS 2022(方言设置为 C++20)上编译:

#include <compare>

namespace N1
{}

namespace N1::N2
{
    class A {};
    A operator-(A&);
}

std::strong_ordering operator-(std::strong_ordering o);

namespace N1
{
    using namespace N2; // (1) !!!

    std::strong_ordering foo();
    inline std::strong_ordering bar()
    {
        return -foo(); // (2) !!!
    }
}

在(2)处,编译器投诉:error C2678: binary '-': no operator found which takes a left-hand operand of type 'std::strong_ordering' (or there is no acceptable conversion).

当 (1) 处的 using namespace 指令被移除时,编译器会很高兴地找到在全局范围内为 std::strong_ordering 类型定义的 operator-

这引发了一系列问题:

  1. 根据语言标准,此 VS 2022 行为是否 (a) 错误,(b) 允许甚至 (c) 强制?
  2. 如果是 (b) 或 (c),怎么办? allow/mandate编译器在全局范围内找到operator-的具体语句有哪些?
  3. 假设 using namespace 指令一直存在,您建议如何解决这个问题?

Live demo

我现在正在通过让 operator- 住在 std:: 来解决这个问题,即:

...
namespace std
{
    strong_ordering operator-(strong_ordering o);
}
...

但是,我不太确定乱用 std:: 命名空间是否是个好习惯;欢迎评论。

您的编译器是正确的,我希望其他编译器也同意。

对于 using-directive 的行为,参见 C++20 [namespace.udir]/2:

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 (6.5.2), the names appear as if they were declared in the nearest enclosing namespace which contains both the using-directive and the nominated namespace.

换句话说,如果 N1 包含 using namespace N2;,那么仅出于非限定名称查找的目的,N2 中的名称将显示为就好像它们在最低共同祖先中一样N1N2 的命名空间。由于 N2N1 内,最低的公共祖先命名空间只是 N1,这意味着 N2 中的 operator- 出现在 N1 内时执行非限定名称查找。

这意味着 operator- 的非限定查找将找到 N2::operator-,并且不会继续到全局命名空间继续搜索其他声明。参见 [basic.lookup.unqual]/1:

In all the cases listed in 6.5.2, the scopes are searched for a declaration in the order listed in each of the respective categories; name lookup ends as soon as a declaration is found for the name. If no declaration is found, the program is ill-formed.

要解决此问题,有两种策略。一种是将你的类型的 operator- 放在声明该类型的命名空间中,这样就可以通过 argument-dependent 查找找到它。但是,不允许将运算符重载添加到 std 命名空间。另一种策略是使用 using-declaration:

重新声明您想要的 operator-
using namespace N2;
using ::operator-;

这有效地带来了您想要的 operator-“更深一层”,将其置于与另一个 operator- 相同的水平,这要归功于 using-directive,因此非限定名称查找将同时找到两者,编译器将执行重载解析。