Ответ 1
Нет, эти два выражения не должны быть синонимами. std::cout << 42
отображается как как operator<<(std::cout, 42)
, так и std::cout.operator<<(42)
. Оба поиска производят жизнеспособных кандидатов, но второй - лучшее совпадение.
Операторы в С++ обычно рассматриваются как альтернативный синтаксис для функций/методов, особенно в контексте перегрузки. Если это так, два приведенных ниже выражения должны быть синонимичными:
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)
Такая ошибка вызывает как минимум два вопроса:
operator<<(basic_ostream<char, _Traits>& __out,
int
__c)
отсутствует?Кажется, что инфиксные и префиксные нотации не являются полностью взаимозаменяемыми - различный синтаксис влечет за собой другую тактику разрешения имен. Каковы различия и откуда они взялись?
Нет, эти два выражения не должны быть синонимами. std::cout << 42
отображается как как operator<<(std::cout, 42)
, так и std::cout.operator<<(42)
. Оба поиска производят жизнеспособных кандидатов, но второй - лучшее совпадение.
Это правила поиска операторов из С++ 17 [over.match.oper/3], где я отредактировал для краткости, удалив текст, не относящийся к перегрузке operator<<
с левым операндом, являющимся типом класса; и выделил раздел, который я объясню позже:
Для бинарного оператора
@
с левым операндом типа, чья cv-безусловная версия -T1
и правым операндом типа чья cv-безоговорочная версия -T2
, три набора функций-кандидатов, назначенные кандидаты в члены, кандидаты, не являющиеся членами, и встроенные -в кандидатах, построены следующим образом:
- Если
T1
является полным типом класса или классом, определяемым в настоящее время, набор кандидатов в члены является результатом квалифицированного поискаT1::[email protected]
(16.3.1.1.1); в противном случае набор кандидатов в члены пуст.- Набор кандидатов, не являющихся членами, является результатом неквалифицированного поиска
[email protected]
в контексте выражения в соответствии с обычными правилами для поиска имени в неквалифицированных вызовах функций, за исключением того, что все функции-члены игнорируются.
Встроенные кандидаты здесь пусты, что относится к функциям поиска, которые неявно преобразуют оба операнда в целочисленные типы и применяют оператор сдвига битов; но не существует неявного преобразования из 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-подавление отсутствует из-за того, что функция-член найдена).Затем выполняется разрешение перегрузки для объединения этих двух наборов.