C++ Ошибка поиска имени перегруженной функции

Я столкнулся со странной ошибкой при поиске имени в C++.

Ошибку можно воссоздать, используя следующий минимальный пример:

#include <vector>
#include <iostream>

std::ostream& operator<<(std::ostream& out, const std::vector<int>& a) {
    for (size_t i = 0; i < a.size(); i++) {
        out << a[i] << std::endl;
    }
    return out;
}

namespace Test {

    struct A {
        // Label 1
        friend std::ostream& operator<<(std::ostream& out, const A&) {
            return out << "A" << std::endl;
        }
    };

    struct B {
        void printVector() noexcept {
            std::vector<int> v{1, 2, 3};
            std::cout << v << std::endl; // The error occurs in this line
        }
        // Label 2
        friend std::ostream& operator<<(std::ostream& out, const B&) {
            return out << "B" << std::endl;
        }
    };

}

int main() {
    Test::B().printVector();
}

Компиляция приведет к следующему сообщению об ошибке:

cannot bind 'std::ostream {aka std::basic_ostream<char>}' lvalue to 'std::basic_ostream<char>&&'

Вы можете сами проверить это здесь: http://cpp.sh/5oya

Странно то, что код компилируется и работает нормально, если вы удалите одну из функций, помеченных // Label 1 соответственно // Label 2.

Теперь мой вопрос: что здесь происходит? Как это можно исправить?


person zuenni    schedule 12.01.2017    source источник
comment
Работает здесь   -  person NathanOliver    schedule 12.01.2017
comment
Visual Studio 2015 выдает это сообщение об ошибке: ошибка C2679: двоичный файл '‹‹': не найден оператор, который принимает правый операнд типа 'std::vector‹int,std::allocator‹_Ty››' (или есть не приемлемое преобразование)   -  person François Andrieux    schedule 12.01.2017
comment
g++ версии 5.3.1 выдает это сообщение об ошибке: Error: no match for »operator<<« (operand types are »std::ostream {aka std::basic_ostream<char>}« and »std::vector<int>«)   -  person zuenni    schedule 12.01.2017
comment
Возможный дубликат пространства имен и разрешение оператора   -  person Rama    schedule 12.01.2017
comment
Невозможно воспроизвести с помощью MinGW g++ 6.3.0, но Visual C++ 2015 имеет ту же ошибку. Как и онлайн-версия g++ 4.9.2, которую вы использовали, она учитывает только basic_ostream::operator<< и std::operator<<. Что странно. Еще более странно: он находит перегрузку, если он помещен в пространство имен Test, т. е. он действительно ищет во внешних пространствах имен, а не только в классе и ADL. И это происходит также с онлайн g++ 4.9.2.   -  person Cheers and hth. - Alf    schedule 12.01.2017


Ответы (2)


Другим обходным решением будет перегрузка оператора ‹‹ внутри пространства имен std, а не в глобальном пространстве имен. (Поиск найдет его в области пространства имен)

namespace std
{
    std::ostream& operator<<(std::ostream& out, const std::vector<int>& a) {
        for (size_t i = 0; i < a.size(); i++) {
            out << a[i] << std::endl;
        }
        return out;
    }
}

Попробуйте здесь

[ОТРЕДАКТИРОВАНО]

Другой обходной путь для пуритан, которые не хотят загрязнять глобальное пространство имен или std, заключается в следующем:

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

... как обсуждалось здесь.

namespace Test
{
    std::ostream& operator<<(std::ostream& out, const std::vector<int>& a) {
        for (size_t i = 0; i < a.size(); i++) {
            out << a[i] << std::endl;
        }
        return out;
    }
}

Рабочий код здесь.

person Rama    schedule 12.01.2017
comment
Мне не нравится, что это требует расширения пространства имен std. Но использование этого обходного пути не требует изменения кода вызова, поэтому я буду использовать это решение. Большое спасибо. - person zuenni; 13.01.2017
comment
@Rama, это решение фактически вызывает неопределенное поведение, поэтому оно не работает*. Вы можете специализировать любой шаблон в пространстве имен std::, но вам не разрешено перегружать какую-либо функцию в пространстве std:: nemespace. Стандарт ISO C++ вызывает поведение Undefined для любой программы C++, которая добавляет что-либо к пространство имен std::. - person WhiZTiM; 13.01.2017
comment
@WhiZTiM Ооо!! то есть вы думаете, что делать голый в глобальной области видимости будет больше, чем в конкретном пространстве имен? - person Rama; 13.01.2017

У вас проблемы с ADL, хотя я думаю, что поиск имени должен был найти вашу перегрузку в глобальном пространстве имен. (У меня нет конкретных стандартных кавычек C++, чтобы узнать, не ошибся ли компилятор). Компиляторы GCC (версия 6.3) и Clang (версия 3.8.0) находят перегрузку, как показано здесь

Один из обходных путей — импортировать name глобального оператора в ваше текущее пространство имен:

std::vector<int> v{1, 2, 3};
using ::operator <<; 
std::cout << v << std::endl;

Как показано здесь: http://cpp.sh/95kuq

Другой обходной путь будет заключаться в явном вызове глобальной перегрузки, например:

std::vector<int> v{1, 2, 3};
::operator << (std::cout,  v); 
std::cout << std::endl;

Как показано здесь: http://cpp.sh/4btyq

person WhiZTiM    schedule 12.01.2017
comment
Да, это работает, но убирает красоту использования перегруженных операторов. - person zuenni; 12.01.2017
comment
@zuenni, вы можете просто импортировать глобальный оператор, такой как using ::operator<<;, как показано здесь. Я пересмотрел свой ответ, чтобы отразить это - person WhiZTiM; 13.01.2017
comment
Спасибо за ваши решения. Оба действительно работают. Но я предпочитаю решение, в котором вызывающий код может оставаться неизменным и выглядеть так, как и следовало ожидать. - person zuenni; 13.01.2017