Как работает разрешение перегрузки оператора в пространствах имен?
Я обнаружил странное поведение 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++.