I have this code:
#include <iostream>
namespace FooStuff
{
struct Foo { };
}
decltype(std::cout)& operator << (decltype(std::cout)& left, const FooStuff::Foo& right)
{
return left;
}
void test1()
{
// This works fine
std::cout << FooStuff::Foo();
}
As far as I can tell, this is the best operator <<
that one could possibly write down to match the call in test1
, and it works as you would expect.
Now add this code below the code above:
namespace BarStuff
{
struct Bar { };
// Danger zone
void test2()
{
// This works too, for now
std::cout << FooStuff::Foo();
}
}
The call in test2
works too, as you would expect.
But if I insert the following operator right below the "Danger zone" comment, everything breaks:
Bar operator << (Bar left, Bar right)
{
return left;
}
Now the call in test2
won't compile because the compiler chooses the completely inappropriate overload that takes a bunch of Bar
s, even though the operator from the first snippet should be a perfect match:
main.cpp:33: error: invalid operands to binary expression ('std::ostream' (aka 'basic_ostream<char>') and 'FooStuff::Foo')
(A ton of unrelated overloads omitted for brevity)
main.cpp:25: candidate function not viable: no known conversion from 'std::ostream' (aka 'basic_ostream<char>') to 'BarStuff::Bar' for 1st argument
What the poop is going on? Why does the compiler choose that overload instead of the one I want it to choose?
And why does the error disappear, if Foo
is moved outside FooStuff
?
CodePudding user response:
When you write std::cout << FooStuff::Foo();
name lookup is done to determine the candidates for <<
to use in overload resolution.
For overload resolution of operators there are two parts to this lookup: unqualified name lookup of operator<<
and argument-dependent lookup of operator<<
.
For unqualified name lookup, as is always the case, lookup traverses from inner to outer scope and stops as soon as a match is found. So if you put an overload at // Danger zone
, the one outside BarStuff
will be hidden.
For argument-dependent name lookup, all overloads in the class types of the operands and their immediately enclosing namespace will be considered. In your case that means that overloads inside struct Foo
and namespace FooStuff
will be found as well, no matter where the std::cout << FooStuff::Foo();
is placed.
For the above reasons, operator overloads should be placed in the namespace containing the class for which they overload the operator. This assures that the overload is always found via argument-dependent lookup.