`std::cout << FooStuff::Foo();` 选择完全不相关的重载而不是完全匹配的重载

`std::cout << FooStuff::Foo();` chooses completely unrelated overload instead of exactly matching one

我有这个代码:

#include <iostream>

namespace FooStuff
{
    struct Foo { };
}

decltype(std::cout)& operator << (decltype(std::cout)& left, const FooStuff::Foo& right)
{
    return left;
}

void test1()
{
    // This works fine
    std::cout << FooStuff::Foo();
}

据我所知,这是最好的 operator << 可以写下来以匹配 test1 中的调用,并且它的工作方式与您预期的一样。

现在在上面的代码下面添加这段代码:

namespace BarStuff
{
    struct Bar { };

    // Danger zone

    void test2()
    {
        // This works too, for now
        std::cout << FooStuff::Foo();
    }
}

test2 中的调用也如您所料。

但是如果我在“危险区域”注释的正下方插入以下运算符,一切都会中断:

    Bar operator << (Bar left, Bar right)
    {
        return left;
    }

现在 test2 中的调用不会编译,因为编译器选择了完全不合适的重载,它需要一堆 Bars,即使第一个片段中的运算符应该是完美的匹配:

main.cpp:33: error: invalid operands to binary expression ('std::ostream' (aka 'basic_ostream<char>') and 'FooStuff::Foo')
(A ton of unrelated overloads omitted for brevity)
main.cpp:25: candidate function not viable: no known conversion from 'std::ostream' (aka 'basic_ostream<char>') to 'BarStuff::Bar' for 1st argument

大便怎么回事?为什么编译器选择那个重载而不是我希望它选择的重载?

如果 Foo 移到 FooStuff 之外,为什么错误会消失?

当您编写 std::cout << FooStuff::Foo(); 时,将进行名称查找以确定 << 的候选者以用于重载解析。

对于运算符的重载解析,此查找有两个部分:operator<< 的非限定名称查找和 operator<<.

的 argument-dependent 查找

对于非限定名称查找,通常情况下,查找从内部范围遍历到外部范围,并在找到匹配项后立即停止。因此,如果您在 // Danger zone 放置一个重载,BarStuff 之外的那个将被隐藏。

对于 argument-dependent 名称查找,将考虑操作数的 class 类型及其紧邻命名空间中的所有重载。在你的情况下,这意味着 struct Foonamespace FooStuff 内的重载也会被发现,无论 std::cout << FooStuff::Foo(); 放在哪里。

出于上述原因,运算符重载应放置在包含 class 的名称空间中,它们为此运算符重载。这确保总是通过 argument-dependent 查找找到过载。