Вопрос:

Синтаксис Infix против префикса: различия в поиске имен

c++ syntax operator-overloading iostream

605 просмотра

2 ответа

926 Репутация автора

Операторы в C ++ обычно рассматриваются как альтернативный синтаксис для функций / методов, особенно в контексте перегрузки. Если это так, два выражения ниже должны быть синонимами:

std::cout << 42;
operator<<(std::cout, 42);

На практике второе утверждение приводит к следующей ошибке:

call of overloaded ‘operator<<(std::ostream&, int)’ is ambiguous

Как обычно, такое сообщение об ошибке сопровождается списком возможных кандидатов, это:

operator<<(basic_ostream<_CharT, _Traits>& __out, char __c)
operator<<(basic_ostream<char, _Traits>& __out, char __c)
operator<<(basic_ostream<char, _Traits>& __out, signed char __c)
operator<<(basic_ostream<char, _Traits>& __out, unsigned char __c)

Такая ошибка вызывает как минимум два вопроса:

  1. Чем отличаются эти два утверждения (с точки зрения поиска имени)?
  2. Почему отсутствует?operator<<(basic_ostream<char, _Traits>& __out,int__c)

Кажется, что инфиксные и префиксные нотации не являются полностью взаимозаменяемыми - разный синтаксис влечет за собой разные тактики разрешения имен. В чем различия и откуда они взялись?

Автор: Krzysztof Abramowicz Источник Размещён: 17.08.2014 07:03

Ответы (2)


15 плюса

34220 Репутация автора

Решение

Нет, два выражения не должны быть синонимами. std::cout << 42выглядит как operator<<(std::cout, 42)и std::cout.operator<<(42). Оба поиска дают жизнеспособных кандидатов, но второй - лучший выбор.

Автор: Igor Tandetnik Размещён: 17.08.2014 07:05

0 плюса

108011 Репутация автора

Это правила поиска операторов из C ++ 17 [over.match.oper / 3], где я отредактировал для краткости, удалив текст, который не имеет отношения к перегрузке operator<<с левым операндом, являющимся типом класса; и выделил раздел, который я объясню позже:

Для бинарного оператора @с левым операндом типа, чья версия cv-unqualified является, T1и правым операндом типа, чья версия cv-unqualified есть T2, три набора функций-кандидатов, назначенные кандидаты-члены, кандидаты , не являющиеся членами, и встроенные кандидаты , построены следующим образом:

  • Если T1является полным типом класса или классом, определяемым в настоящее время, набор кандидатов в члены является результатом квалифицированного поиска T1::operator@(16.3.1.1.1); в противном случае набор кандидатов в члены пуст.
  • Набор кандидатов, не являющихся членами, является результатом неквалифицированного поиска operator@в контексте выражения в соответствии с обычными правилами поиска имен в неквалифицированных вызовах функций, за исключением того, что все функции-члены игнорируются .

Встроенные кандидаты здесь пусты, что относится к функциям поиска, которые неявно преобразуют оба операнда в целочисленные типы и применяют оператор сдвига битов; но не существует неявного преобразования из iostreams в целочисленный тип.

Набор функций-кандидатов для разрешения перегрузки представляет собой объединение кандидатов в члены, кандидатов, не являющихся членами, и встроенных кандидатов.


В чем причина того, что поиск операторов отличается от поиска других функций, и что все это значит? Я думаю, что лучше всего ответить на пару примеров. Во-первых:

struct X{ operator int(); };

void f(X);

struct A
{
    void f(int);

    void g() { X x; f(x); }    // Calls A::f
};

В этом примере есть принцип: если вы пытаетесь вызвать функцию-член класса из другой функции-члена класса; он определенно должен найти эту функцию-член, и не должен иметь поиск, загрязненный внешними функциями (даже включая ADL).

Таким образом, часть безусловных правил поиска состоит в том, что если не-ADL часть поиска находит функцию-член класса, то ADL не выполняется.

Без этого правила можно f(x)было бы найти и то, A::fи другое, а ::fзатем разрешение перегрузки выбрало бы ::fлучшее соответствие, чего мы не хотим.

На второй пример:

struct X{};
std::ostream& operator<<(std::ostream&, X);

struct S
{
    std::ostream& operator<<(int);

    void f()
    {
         X x;
         std::cout << x;   // OK
         // operator<<(std::cout, x);  // FAIL
         // std::cout.operator<<(x);   // FAIL
    }
};

В соответствии с принципом предыдущего примера - если бы правила были просто std::cout << 42;преобразованы, operator<<(std::cout, 24);то поиск по имени нашел бы S::operator<<и остановил. Упс!

Поэтому я думаю, что не совсем правильно говорить, что поведение приведенной OKвыше строки происходит от выполнения обеих отмеченных строк FAIL, как предлагали другие ответы / комментарии.


РЕЗЮМЕ:

Теперь мы можем понять фактическую формулировку стандартной цитаты в верхней части моего ответа.

Код std::cout << x;будет:

  • Посмотрите , как std::cout.operator<<(x); и
  • Посмотрите, как operator<<(std::cout, x)ИСКЛЮЧЕНО ТО, что функции-члены игнорируются (и, следовательно, нет ADL-подавления из-за обнаружения функции-члена).

Затем выполняется разрешение перегрузки для объединения этих двух наборов.

Автор: M.M Размещён: 13.06.2019 03:37
Вопросы из категории :
32x32