Как работает разрешение перегрузки оператора в пространствах имен?

Я обнаружил странное поведение C++ разрешения перегрузки операторов, я не могу объяснить себя. Указатель на некоторый ресурс, описывающий его, был бы так же хорош, как и ответ.

У меня есть 2 единицы перевода. В одном (называемом util.cpp / h) я объявляю и определяю два оператора (я опускаю реальные реализации для читабельности, в любом случае проблема возникает):

// util.h
#ifndef GUARD_UTIL
#define GUARD_UTIL

#include <iostream>

std::istream& operator>>(std::istream& is, const char* str);
std::istream& operator>>(std::istream& is, char* str);
#endif

А также:

//util.cpp
#include "util.h"
#include <iostream>

std::istream& operator>>(std::istream& is, const char* str) {
  return is;  
}
std::istream& operator>>(std::istream& is, char* str) {
  return is;  
}

Эти операторы, разумеется, находятся в глобальном пространстве имен, поскольку они работают со стандартными типами и встроенными типами и должны использоваться везде. Они просто отлично работают из глобального пространства имен (например, из main()) или с явным указанием компилятору, что они находятся в глобальном пространстве имен (см. Пример кода).

В другом модуле перевода (называемом test.cpp / h) я использую эти операторы в пространстве имен. Это работает, пока я не помещу подобный оператор в это пространство имен. Как только этот оператор добавлен, компилятор (например, gcc или clang) больше не может найти жизнеспособный оператор >>.

// test.h
#ifndef GUARD_TEST
#define GUARD_TEST

#include <iostream>

namespace Namespace {
  class SomeClass {   
    public:
      void test(std::istream& is);
  };

  // without the following line everything compiles just fine
  std::istream& operator>>(std::istream& is, SomeClass& obj) { return is; }; 
}

#endif

А также:

//test.cpp
#include "test.h"
#include "util.h"
#include <iostream>

void Namespace::SomeClass::test(std::istream& is) {
  ::operator>>(is, "c"); //works
  is >> "c" //fails
}

Почему компилятор находит правильный оператор, когда в пространстве имен нет оператора >>, но не может найти его, когда он есть? Почему оператор влияет на способность компилятора найти правильный, даже если у него другая подпись?

Одна попытка исправить это было поставить

std::istream& operator>>(std::istream& is, const char* str) {::operator>>(is, str); }

в пространство имен, но потом компоновщик жалуется на предыдущие определения. Итак, дополнительно: почему компоновщик может найти то, что компилятор не находит?

3 ответа

Решение

Это вопрос, скрывающий имя. Стандарт гласит (C++03, 3.3.7/1)

Имя может быть скрыто явным объявлением того же имени во вложенной декларативной области или производном классе (10.2).

"Имя" в вашем случае будет operator>> и пространства имен составляют вложенные декларативные регионы.

Самый простой способ исправить это - использовать using объявление, в котором вы объявляете пространство имен локальным operator<<:

namespace your_namespece {
    std::istream& operator>>(std::istream& is, SomeClass& obj) { return is; }; 
    using ::operator>>;
}

Обратите внимание, что эта функция не мешает поиску Кенига (по крайней мере, в вашем случае, в принципе, это возможно), поэтому операторы ввода-вывода из std:: все еще будет найден.

PS: Еще одна возможность для решения этой проблемы будет определение оператора для SomeClass как встроенныйfriend, Такие функции объявляются на уровне пространства имен (вне "своего" класса), но оттуда не видны. Их можно найти только по поиску Кенига.

Здесь есть несколько вопросов; для начала, вы переопределяете функцию в глобальном пространстве имен, которая уже существует в std::, Однако проблема, которую вы описываете, связана с тем, как работает поиск имен. По сути, в случае перегрузки операторов компилятор выполняет два поиска имен. Первый (используется для всех символов, а не только для операторов) начинается в области, в которой появляется символ, и работает наружу: сначала локальные блоки, затем класс и его базовые классы (если есть), и, наконец, пространства имен, выработка в глобальном пространстве имен. Важной характеристикой этого поиска является то, что он останавливается в любой области, в которой он находит имя: если он находит имя в локальной области, он не ищет ни в каких классах; если он находит его в классе, он не смотрит в базовые классы или пространства имен, а если он находит его в пространстве имен, он не смотрит ни в какие вложенные пространства имен. Что касается этого поиска, все перегрузки должны быть в одной области. Второй поиск влияет только на функции и перегрузки операторов и происходит в контексте классов или объектов, используемых в качестве аргументов; таким образом, если один из операндов является классом в стандартной библиотеке (или чем-либо производным от класса в стандартной библиотеке), компилятор будет искать функции в std::даже если контекст, в котором используется символ, не включает std::, Проблема в том, что встроенные типы, такие как char*, не подразумевайте никакого пространства имен (даже глобального): учитывая ваши перегрузки, первый поиск остановится на первом operator>> он видит, а второй будет смотретьтолько в std::, Ваша функция ни в одном. Если вы хотите, чтобы был найден перегруженный оператор, вы должны определить его в области действия одного из его операндов.

Конкретно, здесь: вы не можете перегружать std::istream& operator>>( std::istream&, char* )потому что он уже перегружен в стандартной библиотеке. std::istream& operator>>( std::istream&, char const* ) возможно, но я не уверен, что он должен делать, так как он не может записать второй операнд. В более общем смысле, вы должны перегружать этот оператор только для определенных вами типов, и вы должны поместить свою перегрузку в то же пространство имен, что и сам тип, так, чтобы он был найден во втором поиске (называемом Argument Dependent Lookup, или ADL). - или раньше, поиск Кенига, после человека, который его изобрел).

:: является глобальной областью действия, поэтому компилятор должен сканировать глобальное пространство имен и найти этот оператор. is >> "C", пытающийся найти оператор >> в пространстве имен, поэтому компилятор находит его и прекращает поиск, затем компилятор пытается выбрать оператор с необходимой сигнатурой, если такого оператора нет - компилятор завершится неудачно. Я думаю, что вы должны прочитать Herb Sutter Exceptional C++.

Другие вопросы по тегам