Namespaces and operator resolution
Asked Answered
N

4

63

I am using a library that defines output stream operators (operator<<) in the global namespace. In my own namespace, I was always declaring such operators in the global namespace, and never had problems with them. But now for various reasons I need to declare those operators in my own namespace, and suddenly, the compiler can't seem to find the operators declared in the library anymore.

Here's a simple example that illustrates my problem:

#include <iostream>

namespace A
{
   struct MyClass {};
}

std::ostream & operator<<( std::ostream & os, const A::MyClass & )
   { os << "namespace A"; return os; }

namespace B
{
   struct MyClass {};

   std::ostream & operator<<( std::ostream & os, const B::MyClass & )
      { os << "namespace B"; return os; }
}

namespace B
{
   void Test()
   {
      std::cout << A::MyClass() << std::endl;
      std::cout << B::MyClass() << std::endl;
   }
}

int main()
{
   B::Test();
   return 1;
}

I am getting the following error:

error: no match for ‘operator<<’ in ‘std::cout << A::MyClass()’

Note that if both operators are within the namespaces, or alternatively if they are both in the global namespace, the code compiles and executes correctly.

I'd really like to understand what's going on and also what the "good practice" for defining such operators with namespaces.

Thanks!

Nuke answered 4/3, 2011 at 15:12 Comment(1)
+1 Very strange! If I put Test in namespace A, it compiles and executes correctly!Coquina
A
61

Since Test is within namespace B the compile sees the operator in that namespace and notes that it doesn't have a matching signature. It also attempts to find the operator in namespace A which contains the class but can't find it there either. Because there's already such an operator (with the wrong signature) in namespace B it won't go and attempt to find one at global scope.

The reason it doesn't search for the global one is roughly as follows. I'm first going to quote the standard and then try to explain it.

From 3.4/1:

...Name lookup may associate more than one declaration with a name if it finds the name to be a function name; the declarations are said to form a set of overloaded functions (13.1). Overload resolution (13.3) takes place after name lookup has succeeded.

As I read this, when the compiler is trying to find a function (which your operators is in this context) it first tries to do name lookup to find the function first. Then next it tries to pick the right function from the set of overloads.

Now from 3.4.1/6:

A name used in the definition of a function(26) that is a member of namespace N (where, only for the purpose of exposition, N could represent the global scope) shall be declared before its use in the block in which it is used or in one of its enclosing blocks (6.3) or, shall be declared before its use in namespace N or, if N is a nested namespace, shall be declared before its use in one of N’s enclosing namespaces.

Let's break this down. You're using the operator<< in a namespace level function so this section applies. It's going to try to find that operator using the precedence in the described above. Your operator isn't declared in the current block or enclosing blocks (this is referring to nested {} within your function). However the next part matches "...shall be declared before its use in namespace N...". There is in fact an operator<< in the current namespace (B) so it adds that operator to its list of matches. There aren't any more matches in B, and because same-namespace scope is considered the best possible match-closeness, it won't look into any other scopes.

The reason it works when you put the operator into namespace A is that since the item being printed is a member of A, that namespace is actually considered because it's included in the namespaces of the expression. Since namespace A is considered it finds the appropriate match in that namespace and compiles correctly.

Now that it has a list of possible operators, it tries to do overload resolution on them. Unfortunately the one in found in namespace B is the only one it considers and it doesn't match the required arguments.

In general you should have the insertion operators in the same namespace as the class upon which it operates.

Aphrodisiac answered 4/3, 2011 at 15:24 Comment(5)
+1. Also mention the importance/role of Koenig Lookup here!Tamworth
Yes there is an operator<< in namespace B, but it does not have the correct signature, so why is the compiler not looking in the global namespace? It would if B::operator<< wasn't thereNuke
You put the code in namespaces for a reason. We don't really want collisions with all the names in the global namespace!Kato
Thanks for the answer, @mark-b. This problem seems easily resolved by putting the << overloads in the global namespace. I'm just getting back into C++, but this even makes sense to me. Is there a reason to not do this?Widdershins
This answer explains the problem, but does not solve it. A solution is in using ::operator<<; in namespace B, see https://mcmap.net/q/319449/-namespaces-and-operator-resolutionSebastian
S
14

The problem has been explained in the answer by @Mark B. The following solves the problem. In the namespace where you want to use the global operator<<, type the following code:

using ::operator<<;

In the OP's code example that line of code would go to the location along the other code which declares/defines operator<< for namespace B:

namespace B
{
   struct MyClass {};

   std::ostream & operator<<( std::ostream & os, const B::MyClass & )
      { os << "namespace B"; return os; }

   using ::operator<<;
}
Sebastian answered 28/9, 2015 at 11:54 Comment(1)
I personally would prefer not pulling in the global operator generally together with the class definition, rather only when it is needed for resolution (so in question's example example, before the function void Test()). Sure, we might have to do this multiple times then, still looks cleaner to me...Gatepost
P
13

My answer is very similar to the others but in particular, the compiler is trying to find A::operator<<() because it's operating on something in the A namespace. If you want to invoke the one outside the namespace, you can call it explicitly by using

::operator<<(std::cout, A::MyClass();

For smoother syntactic use, put it in the namespace.

Pyriphlegethon answered 4/3, 2011 at 15:58 Comment(0)
G
1

It's because your first operator<<() is defined outside the namespace A.

Gallinule answered 4/3, 2011 at 15:21 Comment(1)
"Note that if both operators are within the namespaces, or alternatively if they are both in the global namespace, the code compiles and executes correctly." He is looking for a rationale behind why it doesn't work; he already knows how to fix it.Coquina

© 2022 - 2024 — McMap. All rights reserved.