16

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

这有效:

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(尽管目前我假设这种行为符合标准)。

4

2 回答 2

13

在参数相关查找(koenig 查找的正确名称)中,编译器将在每个参数的命名空间中声明的函数添加到重载函数集。

在您的情况下,第一个operator<<在命名空间中声明,thesus::core,这是您调用运算符的参数的类型。因此,这operator<<被考虑用于 ADL,因为它是在关联的命名空间中声明的

在第二种情况下,operator<<似乎是在全局命名空间中声明的,它不是关联的命名空间,因为参数 1 的类型来自命名空间std,参数 2 的类型来自命名空间theseus::core

实际上,可能您的 2ndoperator<<没有在全局命名空间中声明,因为这可以通过查看父范围来找到。也许您有更多类似的东西?如果您可以发布更多代码,我们可以给出更好的答案。


好的,我记得,当 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;
    }  
}
于 2011-01-05T12:34:07.307 回答
1

运算符重载就像一个函数但有所不同,其中一个区别是命名空间查找。

与函数一样,运算符重载也属于命名空间,但确定函数范围的方式是不切实际的。想象一下,如果您的代码必须调用

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

因此,<<运算符必须位于参数之一的名称空间中,在这种情况下, std(对于cout)或 的名称空间。pthesus::core

这就是 Koenig 查找原理。您必须在正确的命名空间中定义运算符重载。

于 2011-01-05T12:53:16.460 回答