Правила поиска операторов С++/поиск Koenig

При написании набора тестов мне нужно было обеспечить реализацию operator<<(std::ostream&... для Boost unit test для использования.

Это сработало:

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 unit test, но здесь тестовый код:

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 (хотя на данный момент я предполагаю, что это поведение соответствует стандартам).

Ответ 1

При зависимом от аргумента поиске (правильное имя для поиска koenig) компилятор добавляет к перегруженной функции набор функций, которые объявлены в пространствах имен каждого параметра.

В вашем случае первый operator<< объявляется в пространстве имен thesus::core, который является типом аргумента, с которым вы вызываете оператор. Поэтому этот operator<< считается для ADL, потому что он объявлен в связанном пространстве имен

Во втором случае operator<< кажется, объявлен в глобальном пространстве имен, которое не является связанным пространством имен, так как параметр один имеет тип из пространства имен std а параметр 2 имеет тип из пространства имен theseus::core.

На самом деле, вероятно, ваш второй operator<< не объявлен в глобальном пространстве имен, так как он будет найден при просмотре в родительских областях. Может быть, у вас есть что-то более похожее на это? Если вы можете разместить больше кода, мы можем дать лучший ответ.


Хорошо, я вспомнил, ADL не ищет в родительских областях, когда находит имя в текущей области. Таким образом, макрос повышения 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

Перегрузка оператора похожа на функцию, но отличается, и одна из отличий - поиск пространства имен.

Подобные функции, перегрузки операторов принадлежат пространству имен, но показ того, как вы выполняете функцию, будет непрактичным. Представьте, должен ли ваш код вызывать

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

Поэтому оператор << должен находиться в пространстве имен одного из параметров: std (для cout) или пространства имен p, в данном случае thesus::core.

Это принцип поиска Koenig. Вы должны определить перегрузку оператора в правильном пространстве имен.