C++ 运算符查找规则 / Koenig 查找

16

在编写测试套件时,我需要为Boost单元测试提供operator<<(std::ostream&...的实现。

这段代码有效:

namespace theseus { namespace core {
    std::ostream& operator<<(std::ostream& ss, const PixelRGB& p) {
        return (ss << "PixelRGB(" << (int)p.r << "," << (int)p.g << "," << (int)p.b << ")");
    }
}}

这个没有:

std::ostream& operator<<(std::ostream& ss, const theseus::core::PixelRGB& p) {
    return (ss << "PixelRGB(" << (int)p.r << "," << (int)p.g << "," << (int)p.b << ")");
}

显然,当g++试图解析操作符的使用时,第二个并没有包含在候选匹配中。为什么会这样(是什么规则导致了这种情况)?

调用operator<<的代码深入到了Boost单元测试框架中,但以下是测试代码:

BOOST_AUTO_TEST_SUITE(core_image)

BOOST_AUTO_TEST_CASE(test_output) {
    using namespace theseus::core;
    BOOST_TEST_MESSAGE(PixelRGB(5,5,5)); // only compiles with operator<< definition inside theseus::core
    std::cout << PixelRGB(5,5,5) << "\n"; // works with either definition
    BOOST_CHECK(true); // prevent no-assertion error
}

BOOST_AUTO_TEST_SUITE_END()

供参考,我正在使用g++ 4.4(虽然目前我假设这种行为符合标准)。


你是在哪个作用域中尝试使用你的 operator<< - CB Bailey
@Charles:我已经编辑添加了测试代码。 - John Bartholomew
2个回答

14
在参数依赖查找(Koenig查找的正确名称)中,编译器将在每个参数的命名空间中声明的函数添加到重载函数集中。
在您的情况下,第一个operator<<声明在命名空间thesus::core中,这是您调用运算符的参数类型。因此,由于它在关联的命名空间中声明,因此会考虑使用此operator<<进行ADL。
在第二种情况下,operator<<似乎是在全局命名空间中声明的,这不是与参数一来自命名空间std和参数二来自命名空间theseus::core的相关命名空间。
实际上,也许您的第二个operator<<并非在全局命名空间中声明,因为通过查看父级范围可以找到它。也许您的代码更像是这样的?如果您可以发布更多代码,我们可以提供更好的答案。
好的,我记得,当ADL在当前作用域中找到名称时,它不会在父作用域中查找。因此,boost宏BOOST_TEST_MESSAGE会扩展以包括operator<<,并且在表达式和全局范围之间存在一个非可行的operator<<。我更新了代码以说明这一点(希望如此)。
#include <iostream>

namespace NS1
{
  class A
  {};

  // this is found by expr in NS2 because of ADL
  std::ostream & operator<<(std::ostream &, NS1::A &);
}


// this is not seen because lookup for the expression in NS2::foo stops when it finds the operator<< in NS2
std::ostream & operator<<(std::ostream &, NS1::A &);

namespace NS2
{
    class B
    {};

    // if you comment this out lookup will look in the parent scope
    std::ostream & operator<<(std::ostream &, B &);

    void foo(NS1::A &a)
    {
        std::cout << a;
    }  
}

2
没错,全局命名空间没有关联是因为boost单元测试工具不是全局的。这是一种特性,可以防止将函数意外注入到模板代码中。 - ltjax
嗯...好的,全局变量没有关联,但是在全局范围内找到一个可行的operator<<操作符应该通过父级作用域查找,不是吗? - dancl
1
(未被接受——当父级/全局范围的事情得到澄清时,我会重新接受) - John Bartholomew
@dancl:在SO上进行代码格式化是通过将代码缩进四个空格来实现的,而不是使用<code>标签。 - Björn Pollex
@danci:那就解释了。谢谢。 - John Bartholomew
显示剩余2条评论

1

运算符重载类似于函数,但有些不同之处,其中一个区别是命名空间查找。

像函数一样,运算符重载也属于命名空间,但是像函数那样进行作用域限制是不可行的。想象一下,如果您的代码必须调用

std::cout thesus::core::<< p; // ouch and obviously incorrect syntax

因此,<< 运算符必须在其中一个参数的命名空间中,可以是 std(用于 cout),也可以是 p 的命名空间,在这种情况下为 thesus::core
这就是 Koenig 查找原则。您必须在正确的命名空间中定义运算符重载。

2
解决重载运算符的查找规则与解决重载函数的规则完全相同。 - CB Bailey
@Charles:是的,尽管名称隐藏行为略有不同。实际上,这并不是那么微妙... https://dev59.com/goDba4cB1Zd3GeqPFow4 - Lightness Races in Orbit

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接