Где генерируется эта функция шаблона? Можно скомпилировать с помощью g ++, но не в Visual Studio - PullRequest
2 голосов
/ 02 июня 2019

Следующий код не может скомпилироваться в моей Visual Studio 2019. Но если я удаляю первую перегрузку >>, он скомпилируется.

Код можно скомпилировать с помощью g ++, что меня смутило. Я предполагаю, что речь идет о разных местах, где функция шаблона генерируется компилятором?

Сообщение об ошибке: ошибка C2679: двоичный файл «>>»: не найден оператор, который принимает правый операнд типа 'std::vector<int,std::allocator<_T>>'

#include <iostream>
#include <vector>
typedef std::vector<int> Mon;  // ordered
typedef std::vector<Mon> Poly;  // ordered

class A {};

// It would compile successfuly if this function is removed
std::istream& operator>>(std::istream& sin, A& a)
{
    return sin;
}

template <typename Container>
void load(std::istream& sin, Container& cont)
{
    typename Container::value_type n;
    sin >> n;
}

std::istream& operator>>(std::istream& sin, Mon& mon)
{
    load(sin, mon);
    return sin;
}

std::istream& operator>>(std::istream& sin, Poly& poly)
{
    load(sin, poly);
    return sin;
}

int main()
{
    return 0;
}

1 Ответ

2 голосов
/ 02 июня 2019

Основная проблема заключается в том, что эта сигнатура функции в глобальном пространстве имен:

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]), применяются следующие правила:

  1. Считаются любые объявления функций, видимые в точке определения шаблона.
  2. Любые объявления функций, которые будут найдены ADL в момент создания экземпляра, рассматриваются.
  3. Если есть функции 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 см. В этой превосходной статье .

...