Infix vs prefix syntax: name lookup differences
No, the two expressions should not be synonymous. std::cout << 42
is looked up as both operator<<(std::cout, 42)
and std::cout.operator<<(42)
. Both lookups produce viable candidates, but the second one is a better match.
These are the operator lookup rules from C++17 [over.match.oper/3] where I have edited for brevity by removing text that does not pertain to overloading operator<<
with the left operand being a class type; and bolded a section which I will explain later on:
For a binary operator
@
with a left operand of a type whose cv-unqualified version isT1
and a right operand of a type whose cv-unqualified version isT2
, three sets of candidate functions, designated member candidates, non-member candidates and built-in candidates, are constructed as follows:
- If
T1
is a complete class type or a class currently being defined, the set of member candidates is the result of the qualified lookup ofT1::operator@
(16.3.1.1.1); otherwise, the set of member candidates is empty.- The set of non-member candidates is the result of the unqualified lookup of
operator@
in the context of the expression according to the usual rules for name lookup in unqualified function calls except that all member functions are ignored.
The built-in candidates are empty here, that refers to searching functions that would implicitly convert both operands to integer types and apply the bit-shift operator; but there is no implicit conversion from iostreams to integer type.
The set of candidate functions for overload resolution is the union of the member candidates, the non-member candidates, and the built-in candidates.
What is the rationale is for having operator lookup differ from other function lookup and what does this all mean? I think this is best answered through a couple of examples. Firstly:
struct X{ operator int(); };
void f(X);
struct A
{
void f(int);
void g() { X x; f(x); } // Calls A::f
};
In this example there is a principle: if you try to call a member function of the class from another member function of the class; it should definitely find that member function, and not have the search polluted by outside functions (even including ADL).
So, part of the unqualified lookup rules is that if the non-ADL part of lookup finds a class member function, then ADL is not performed.
Without that rule, f(x)
would find both A::f
and ::f
and then overload resolution would select ::f
as better match, which we don't want.
Onto the second example:
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
}
};
As per the principle of the previous example -- if the rules were just that std::cout << 42;
is transformed to operator<<(std::cout, 24);
then name lookup would find S::operator<<
and stop. Whoops!
So I think it is not quite correct to say that the behaviour of the OK
line above comes from doing both of the lines marked FAIL
, as other answers/comments have suggested.
SUMMARY:
Now we can understand the actual wording of the standard quote at the top of my answer.
The code std::cout << x;
will:
- Look up as
std::cout.operator<<(x);
AND - Look up as
operator<<(std::cout, x)
EXCEPT THAT member functions are ignored (and therefore, there is no ADL-suppression due to member function being found).
Then overload resolution is performed on the union of those two sets.