22

Operators in C++ are usually considered to be an alternative syntax for functions/methods, especially in the context of overloading. If so, the two expressions below should be synonymous:

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

In practise, the second statement leads to the following error:

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

As usual, such error message is accompanied with a list of possible candidates, these are:

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)

Such error raises at least two questions:

  1. In what way are the two statements different (in terms of name lookup)?
  2. Why operator<<(basic_ostream<char, _Traits>& __out,int__c) is missing?

It seems, that infix and prefix notations are not fully interchangeable -- different syntax entails different name resolution tactics. What are the differences and where did they come from?

Krzysztof Abramowicz
  • 1,556
  • 1
  • 12
  • 30
  • 1
    There's another interesting difference between `x << y` and `operator<<(x, y)`: name lookup in for operators in expressions (the first one) *always* performs ADL, whereas lookup for ordinary (free) function calls (the second one) not always performs ADL. [Live example](http://coliru.stacked-crooked.com/a/975907df23216a48) – dyp Aug 17 '14 at 20:56
  • @dyp Great example! It seems that prefix operator invocation within method implies "class-major" name lookup (like plain method invocation «checked»), while infix notation uses "standard" name lookup (including ADL). But why is it so? – Krzysztof Abramowicz Aug 19 '14 at 19:37
  • 1
    "Prefix operator invocation" uses the *usual* unqualified name lookup rules. Those will search the scopes beginning with the nearest enclosing scope, until the name is found in one scope, then it stops (any enclosing scopes then are ignored). If no name has been found, or if the found name is *not* a member function, (pure) ADL is invoked additionally. I think the reason is that member functions are special, and inside the class itself shall take precedence over any free function. But for operators, ADL is essential, and it just wouldn't work well inside classes with overloaded operators. – dyp Aug 19 '14 at 23:59
  • @dyp Further explanation of your example: `operator<<(*this, 2.5);` is inside a member function of `X` , so unqualified lookup finds `X::operator<<`; and the rule [basic.lookup.argdep/3] says that if the unqualified lookup set finds a class member function then ADL is not performed – M.M Jun 13 '19 at 02:48

2 Answers2

16

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.

Igor Tandetnik
  • 50,461
  • 4
  • 56
  • 85
  • What name lookup has to do with it? The error is an ambiguous overload, that is the compiler can't decide which overload to choose. – 101010 Aug 17 '14 at 19:09
  • 2
    `operator<<()` is an operator function found in namespace `std` through ADL. The overloads that take integers are not found there, but only as member functions. That's why the compiler listed those as ambiguous overloads and said `operator<<(ostream&, int)` couldn't be found. – David G Aug 17 '14 at 19:10
  • 1
    @40two `operator<<(std::cout, 42);` doesn't find, via name lookup it performs, as many candidates as `std::cout << 42` does. – Igor Tandetnik Aug 17 '14 at 19:11
  • @IgorTandetnik I can't understand your last comment. – 101010 Aug 17 '14 at 19:13
  • 3
    @40two: the original question is why `std::cout << 42` works while `operator<<(std::cout, 42);` doesn't; why the two aren't the same. The answer is that `std::cout << 42` may be interpreted both as `operator<<(std::cout, 42)` and also as `std::cout.operator<<(42)`. The compiler performs two name lookups, for both forms, then performs overload resolution on all candidates found by both lookups. This is why the difference in name lookup is most significant for the problem at hand, while the failure of overload resolution in one of the two cases is secondary. – Igor Tandetnik Aug 17 '14 at 19:18
  • OK cleared out, thanks. The reason Ι was persistent is that the OP is trying to print 42 :) – 101010 Aug 17 '14 at 19:31
2

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 is T1 and a right operand of a type whose cv-unqualified version is T2 , 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 of T1::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.

M.M
  • 138,810
  • 21
  • 208
  • 365