【问题标题】:C++ operator lookup rules / Koenig lookupC++ 运算符查找规则/Koenig 查找
【发布时间】:2011-01-05 12:16:22
【问题描述】:

在编写测试套件时,我需要提供 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&lt;&lt; 的代码在 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&lt;&lt; 的范围是什么?
  • @Charles:我已经编辑添加了测试代码。

标签: c++ argument-dependent-lookup


【解决方案1】:

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

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

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

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


好的,我记得,当 ADL 在当前作用域中找到名称时,它不会在父作用域中查找。所以 boost 宏 BOOST_TEST_MESSAGE 扩展为包含一个 operator&lt;&lt; 并且在范围树中有一些在表达式和全局范围之间不可行的 operator&lt;&lt;。我更新了代码来说明这一点(希望如此)。

#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;
    }  
}

【讨论】:

  • 没错,全局命名空间没有关联,因为 boost 单元测试的东西不是全局的。这是一项防止将函数意外注入模板代码的功能。
  • hmm.. ok 全局不相关,但是一个可行的操作符
  • 第二个操作符
  • (不接受——我会在父/全局范围的事情被清除后重新接受)
  • @dancl:SO 上的代码格式化通过将代码缩进四个空格来工作,而不是使用&lt;code&gt;-tags。
【解决方案2】:

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

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

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

因此,&lt;&lt; 运算符必须位于参数之一的命名空间中,std(对于 cout)或 p 的命名空间,在本例中为 thesus::core

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

【讨论】:

  • 解析重载运算符的查找规则与重载函数的查找规则完全相同。
  • @Charles:是的,尽管名称隐藏行为略有不同。好吧,不是那么巧妙,真的...stackoverflow.com/q/24168137/560648
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-06-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-03
  • 2016-06-08
  • 1970-01-01
相关资源
最近更新 更多