两个运营商的一些奇怪的冲突<<

Some strange conflict of two operators <<

本文关键字:lt 冲突 两个 运营商      更新时间:2023-10-16

为什么下面的代码不编译?

#include <iostream>
namespace X
{
inline std::wostream & operator<<(std::wostream & stm, int a)
{
stm << L"int";
return stm;
}
}
namespace Y
{
class A
{
};
}
inline std::wostream & operator<<(std::wostream & stream, const Y::A & msg)
{
stream << L"A";
return stream;
}
namespace X
{
void f()
{
Y::A a;
std::wcout << a;
}
}

为什么要删除命名空间 X 中的operator <<会使代码编译?尝试将其注释掉,例如:

namespace X
{
//inline std::wostream & operator<<(std::wostream & stm, int a)
//{
//    stm << L"int";
//    return stm;
//}
}

这些运算符之间的依赖关系是什么?

请参阅实时示例。

编辑1:

我唯一的猜测是,在使用它的同一命名空间中声明的运算符以某种方式隐藏了其他命名空间中的运算符,但我以前从未听说过......

编辑2:

实际上在我的项目中,第二个运算符位于命名空间 Z(但不是全局(中:

...
namespace Z
{
inline std::wostream & operator << (std::wostream & stream, const Y::A & msg)
{
stream << L"A";
return stream;
}
}
namespace X
{
void f()
{
using namespace Z;
Y::A a;
std::wcout << a;
}
}

这会导致相同的编译器错误。

此行为实际上是C++中预期的,以避免不同命名空间中的不同重载引入的意外行为。

这称为名称隐藏。 你可以在这里阅读一个非常好的答案:https://stackoverflow.com/a/1629074/182676

因此,不同命名空间中的重载将相互隐藏。

您可以通过使用以下using使正确的重载对编译器可见来解决此问题:

Y::A a;
using Z::operator<<;
std::wcout << a;

注意:重载运算符的查找与其他注释/答案建议的类成员函数查找有很大不同。有关重载运算符的名称查找规则的介绍,请参阅此答案。

在第一个示例中,std::wcout << aX::f()中,名称查找会找到:

  • 对左操作数的成员函数进行限定查找:std::wostream有一个成员函数operator<<
  • 非限定查找:当前范围在命名空间X中,因此找到X::operator<<,我们到此为止。此阶段仅在找不到名称时检查父命名空间。
  • 参数
  • 依赖查找:参数是std::wcoutY::a的,因此ADL命名空间是stdY的。

因此,重载集包括:

  • (QL( 所有成员函数std::wostream::operator<<
  • (UL( 所有免费功能X::operator<<
  • (ADL( 所有免费函数std::operator<<
  • (ADL(所有自由函数Y::operator<<(如果有的话,也会
  • (。

仅此而已。

这些都找不到参数类型Y::A的匹配项,因此编译失败。

删除X::operator<<时,非限定查找步骤在X中找不到任何内容,因此它会递归地在父命名空间中查找。然后找到::operator<<函数进入重载集,编译成功。

为了避免此问题,通常的过程是将用户定义类型的自由重载运算符放入与定义类型相同的命名空间中,因此在这种情况下,您将执行以下操作:

namespace Y
{
inline std::wostream & operator<<(std::wostream & stream, const A & msg) { .... }
}

然后 ADL 步骤会找到这个函数,即使非限定查找步骤也找到X::operator<<


在第二个示例中,using namespace Z;的确切含义是:

在非限定名称查找期间,这些名称看起来就像是在最近的封闭命名空间中声明的,该命名空间同时包含 using-指令和指定的命名空间。

包含XZ的最近的封闭命名空间是全局命名空间,因此这些名称的行为就像在非限定查找阶段的全局命名空间中一样。

因此,该过程与我对第一个案例的分析没有实质性区别,只有X::operator<<是通过不合格的查找找到的。 同样,这将通过在Y中包含所需的重载来修复,以便 ADL 找到它。