Какой-то странный конфликт двух операторов < - PullRequest
1 голос
/ 10 февраля 2020

Почему следующий код не компилируется?

#include <iostream>

namespace X
{
    inline std::wostream & operator<<(std::wostream & stm, int a)
    {
        stm << L"int";
        return stm;
    }
}

namespace Y
{
    class A
    {
    };
}

inline std::wostream & operator<<(std::wostream & stream, const Y::A & msg)
{
    stream << L"A";
    return stream;
}

namespace X
{
    void f()
    {
        Y::A a;
        std::wcout << a;
    }
}

и почему удаление operator << в пространстве имен X приводит к компиляции кода? Попробуйте закомментировать, например:

namespace X
{
    //inline std::wostream & operator<<(std::wostream & stm, int a)
    //{
    //    stm << L"int";
    //    return stm;
    //}
}

какова зависимость между этими операторами?

см. живой пример .

EDIT1:

Единственное предположение, которое у меня есть, состоит в том, что оператор, объявленный в том же пространстве имен, где он используется, каким-то образом скрывает операторы из других пространств имен, но я никогда не слышал об этом раньше ...

EDIT2:

На самом деле в моем проекте второй оператор находится в пространстве имен Z (но не глобально):

...

namespace Z
{
    inline std::wostream & operator << (std::wostream & stream, const Y::A & msg)
    {
        stream << L"A";
        return stream;
    }
}

namespace X
{
    void f()
    {
        using namespace Z;
        Y::A a;
        std::wcout << a;
    }
}

, что приводит к тому же компилятору ошибка.

Ответы [ 2 ]

1 голос
/ 11 февраля 2020

ПРИМЕЧАНИЕ: поиск перегруженных операторов существенно отличается от поиска функций-членов класса, как предлагается другими комментариями / ответами. См. Этот ответ для ознакомления с правилами поиска имени для перегруженных операторов.

В вашем первом примере std::wcout << a внутри X::f() поиск имени находит:

  • Квалифицированный поиск для функций-членов левого операнда: std::wostream имеет функцию-член operator<<.
  • Неквалифицированный поиск: текущая область находится в пространстве имен X, поэтому X::operator<< найден, и мы остановимся здесь. Этот этап подходит только для проверки родительских пространств имен, если имя не найдено.
  • Поиск, зависящий от аргументов: аргументы std::wcout и Y::a, поэтому пространства имен ADL std и Y.

Таким образом, набор перегрузки состоит из:

  • (QL) Все функции-члены std::wostream::operator<<.
  • (UL ) Все свободные функции X::operator<<.
  • (ADL) Все свободные функции std::operator<<
  • (ADL) Все бесплатные функции Y::operator<< (или будут, если бы они были).

и ничего больше.

Ни один из них не находит соответствия для типа аргумента Y::A, поэтому компиляция не удалась.

Когда вы удаляете X::operator<<, неквалифицированный шаг поиска ничего не находит в X, поэтому он выглядит в родительское пространство имен, рекурсивно. Затем обнаруживается ::operator<<, и эта функция входит в набор перегрузки, и компиляция завершается успешно.

Чтобы избежать этой проблемы, обычной процедурой является помещение свободных перегруженных операторов пользовательских типов в то же пространство имен, что и тип был определен, поэтому в этом случае вы должны выполнить:

namespace Y
{
    inline std::wostream & operator<<(std::wostream & stream, const A & msg) { .... }
}

, и тогда шаг ADL найдет эту функцию, даже если неквалифицированный шаг поиска также найдет X::operator<<.


Во втором примере точное значение using namespace Z;:

При поиске безоговорочного имени имена появляются так, как если бы они были объявлены в ближайшем окружающем пространстве имен, которое содержит как директиву using, так и назначенное пространство имен.

Ближайшее вмещающее пространство имен, содержащее как X, так и Z, является глобальным пространством имен, поэтому имена ведут себя так, как будто в глобальном пространстве имен для фазы безусловного поиска.

Следовательно, этот процесс существенно не отличается от моего анализа для вашего первого случая, и только X::operator<< обнаруживается при неквалифицированном поиске. Опять же, это можно исправить, включив нужную перегрузку в Y, чтобы она была найдена ADL.

1 голос
/ 10 февраля 2020

Такое поведение на самом деле ожидается в C ++, чтобы избежать неожиданного поведения, вызванного различными перегрузками в разных пространствах имен.

Это называется скрытие имени . Вы можете прочитать действительно хороший ответ на эту тему здесь: { ссылка }

Таким образом, перегрузки в разных пространствах имен будут скрывать друг друга.

Это можно исправить, выполнив корректная перегрузка, видимая для компилятора с using:

Y::A a;
using Z::operator<<;
std::wcout << a;
...