Подтвердить что ты не робот

Пространства имен и разрешение оператора

Я использую библиотеку, которая определяет операторы выходного потока (operator < <) в глобальном пространстве имен. В моем собственном пространстве имен я всегда декларировал такие операторы в глобальном пространстве имен и никогда не испытывал проблем с ними. Но теперь по разным причинам мне нужно объявить эти операторы в моем собственном пространстве имен, и вдруг компилятор больше не может найти операторов, объявленных в библиотеке.

Вот простой пример, иллюстрирующий мою проблему:

#include <iostream>

namespace A
{
   struct MyClass {};
}

std::ostream & operator<<( std::ostream & os, const A::MyClass & )
   { os << "namespace A"; return os; }

namespace B
{
   struct MyClass {};

   std::ostream & operator<<( std::ostream & os, const B::MyClass & )
      { os << "namespace B"; return os; }
}

namespace B
{
   void Test()
   {
      std::cout << A::MyClass() << std::endl;
      std::cout << B::MyClass() << std::endl;
   }
}

int main()
{
   B::Test();
   return 1;
}

Я получаю следующую ошибку:

error: no match for ‘operator<<’ in ‘std::cout << A::MyClass()’

Обратите внимание, что если оба оператора находятся в пространствах имен или, если они оба находятся в глобальном пространстве имен, код компилируется и выполняется правильно.

Мне бы очень хотелось понять, что происходит, а также то, что "хорошая практика" для определения таких операторов с пространствами имен.

Спасибо!

4b9b3361

Ответ 1

Так как Test находится в пространстве имен B, компилятор видит оператор в этом пространстве имен и отмечает, что у него нет соответствующей подписи. Он также пытается найти оператор в пространстве имен A, который содержит класс, но не может найти его там. Поскольку там уже такой оператор (с неправильной сигнатурой) в пространстве имен B он не пойдет и не попытается найти его в глобальной области.

Причина, по которой он не ищет глобальный, выглядит примерно следующим образом. Сначала я процитирую стандарт, а затем попытаюсь объяснить его.

Из 3.4/1:

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

Когда я прочитал это, когда компилятор пытается найти функцию (которая у вас есть в этом контексте), она сначала пытается выполнить поиск имени, чтобы сначала найти функцию. Затем он пытается выбрать правильную функцию из набора перегрузок.

Теперь из 3.4.1/6:

Имя, используемое в определении функция (26), являющаяся членом пространство имен N (где, только для Цель экспозиции, N может представляют собой глобальный охват) объявлено перед его использованием в блоке в котором он используется или в одном из его (6.3), или объявлено перед его использованием в пространстве имен N или, если N является вложенным пространством имен, до его использования в одном из Ns, окружающих пространства имен.

Пусть это сломается. Вы используете operator<< в функции уровня пространства имен, поэтому этот раздел применяется. Он попытается найти этого оператора, используя приоритет в описанном выше. Ваш оператор не объявляется в текущем блоке или закрывающих блоках (это относится к вложенному {} внутри вашей функции). Однако следующая часть соответствует "... должна быть объявлена ​​до ее использования в пространстве имен N...". Фактически существует operator<< в текущем пространстве имен (B), поэтому он добавляет этот оператор в список совпадений. В B больше нет совпадений, и поскольку область с одинаковым пространством имен считается наилучшей возможной близостью, она не будет рассматривать любые другие области.

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

Теперь, когда у него есть список возможных операторов, он пытается сделать на них разрешение перегрузки. К сожалению, тот, который находится в пространстве имен B, является единственным, который он считает, и он не соответствует требуемым аргументам.

В общем случае вы должны иметь операторы вставки в том же пространстве имен, что и класс, на котором он работает.

Ответ 2

Мой ответ очень похож на другие, но, в частности, компилятор пытается найти оператор A:: < <(), поскольку он работает с чем-то в пространстве имен A. Если вы хотите вызывать одно за пределами пространства имен, вы можете вызвать его явно, используя

::operator<<(std::cout, A::MyClass();

Для более плавного синтаксического использования поместите его в пространство имен.

Ответ 3

Проблема объясняется в ответе @Mark B. Следующая проблема решена. В пространстве имен, в котором вы хотите использовать глобальный operator<<, введите следующий код:

using ::operator<<;

В примере кода OP эта строка кода переместится в местоположение по другому коду, который объявляет/определяет operator<< для namespace B:

namespace B
{
   struct MyClass {};

   std::ostream & operator<<( std::ostream & os, const B::MyClass & )
      { os << "namespace B"; return os; }

   using ::operator<<;
}

Ответ 4

Это потому, что ваш первый operator<<() определяется вне пространства имен A.