Основная проблема заключается в том, что эта сигнатура функции в глобальном пространстве имен:
std::istream& operator>>(std::istream& sin, std::vector<int>& mon);
не может быть найдено с помощью аргумент-зависимого поиска. Поскольку все аргументы находятся в std
, ADL ищет только std
, а не глобальное пространство имен.
Чтобы избежать подобных проблем, вы можете следовать эмпирическому правилу: не перегружайте операторы таким образом, чтобы они не были найдены ADL. (Следствие: вы не должны пытаться заставить vector<Foo> v; cin >> v;
работать).
Во-первых, обратите внимание, что синтаксис sin >> n
соответствует выполнению operator>>(sin, n)
и sin.operator>>(n)
и объединению всех результатов , как описано полностью здесь .
Код в вопросе очень похож на этот вопрос , и я подведу итоги находящегося там верхнего ответа.
Для этой функции:
template <typename Container>
void load(std::istream& sin, Container& cont)
{
typename Container::value_type n;
sin >> n;
}
В частности, когда происходит поиск operator>>(sin, n)
, operator>>
является зависимым именем , потому что это имя вызова функции, типы аргументов которого зависят от параметра шаблона.
Когда поиск имени применяется к имени зависимой функции (ref: [temp.dep.candidate]), применяются следующие правила:
- Считаются любые объявления функций, видимые в точке определения шаблона.
- Любые объявления функций, которые будут найдены ADL в момент создания экземпляра, рассматриваются.
- Если есть функции
extern
, определенные в другом месте в программе, которые были бы найдены ADL, если бы у них было видимое объявление в момент создания экземпляра, и эти дополнительные объявления повлияли бы на разрешение перегрузки, то программа не определена поведение (без диагностики).
(ПРИМЕЧАНИЕ. Моя первая версия этого ответа неправильно процитировала Правило 3 и поэтому сделала неправильный вывод):
Таким образом, поиск sin >> n
, созданного при вызове load(sin, mon);
, успешен, потому что найдена функция-член std::istream::operator>>(int&)
. (В результате поиска также была обнаружена версия A&
, но при разрешении перегрузки выбирается функция-член).
Проблема возникает с поиском sin >> n
, экземпляром которого является load(sin, poly);
.
Согласно правилу 1, operator>>(std::istream&, A&)
найдено. (Эта функция позже будет исключена из-за разрешения перегрузки, но на данном этапе мы просто выполняем поиск по имени).
Согласно правилу 2 список пространства имен ADL: std
. Таким образом, этот шаг найдет std::operator>>
(различные перегрузки), но не ::operator>>(istream&, Mon&);
, поскольку он не находится в пространстве имен std
.
Правило 3 не применяется, поскольку в namespace std
нет перегрузок, которые бы принимали Mon
.
Итак, правильное поведение:
- Отправленный код должен завершиться неудачей при компиляции из-за несоответствия
sin >> n
при создании экземпляра load(sin, poly);
.
- С пометкой
It would compile successfully if this function is removed
на самом деле не должно быть различий; код по-прежнему не может быть скомпилирован по той же причине.
ВЫВОД: Мне кажется, что:
- clang 8.0.0 ведет себя корректно.
- msvc корректно отклоняет опубликованный код, но неверно принимает измененную версию.
- gcc 9.1.0 неправильно принимает обе версии;
Замечу, что если мы изменим operator>>
на bar
и sin >> n
на bar(sin, n);
, то gcc и msvc корректно отклонят обе версии. gcc даже выдает очень похожее сообщение об ошибке clang.
Таким образом, я предполагаю, что ошибка могла быть неправильным применением перегруженных правил поиска имен операторов - которые немного отличаются от имен не операторов, но никак не относятся к этому примеру кода.
Подробную информацию об этих правилах и поведении MSVC см. В этой превосходной статье .