Alexey Starinsky
Alexey Starinsky

Reputation: 4339

Some strange conflict of two operators <<

Why doesn't the following code compile?

#include <iostream>

namespace X
{
    inline std::wostream & operator<<(std::wostream & stm, int a)
    {
        stm << L"int";
        return stm;
    }
}

namespace Y
{
    class A
    {
    };
}

inline std::wostream & operator<<(std::wostream & stream, const Y::A & msg)
{
    stream << L"A";
    return stream;
}

namespace X
{
    void f()
    {
        Y::A a;
        std::wcout << a;
    }
}

and why removing operator << in namespace X, makes the code compile? Try to comment it out, for example:

namespace X
{
    //inline std::wostream & operator<<(std::wostream & stm, int a)
    //{
    //    stm << L"int";
    //    return stm;
    //}
}

what is the dependency between these operators?

see live example.

EDIT1:

The only guess I have is that the operator declared in the same namespace where it is used hides the operators from other namespaces somehow, but I never heard about that before...

EDIT2:

Actually in my project the second operator is in namespace Z (but not global):

...

namespace Z
{
    inline std::wostream & operator << (std::wostream & stream, const Y::A & msg)
    {
        stream << L"A";
        return stream;
    }
}

namespace X
{
    void f()
    {
        using namespace Z;
        Y::A a;
        std::wcout << a;
    }
}

that results in the same compiler error.

Upvotes: 1

Views: 102

Answers (2)

M.M
M.M

Reputation: 141638

NOTE: lookup of overloaded operators is substantially different to class member function lookup as suggested by other comments/answers. See this answer for an introduction to the name lookup rules for overloaded operators.

In your first example, std::wcout << a inside X::f(), name lookup finds:

  • Qualified lookup for member functions of the left operand: std::wostream has a member function operator<<.
  • Unqualified lookup: the current scope is in namespace X, so X::operator<< is found, and we stop here. This stage only goes up to check parent namespaces if the name is not found.
  • Argument-dependent lookup: the arguments are std::wcout and Y::a, so the ADL namespaces are std and Y.

So the overload set consists of:

  • (QL) All member functions std::wostream::operator<<.
  • (UL) All free functions X::operator<<.
  • (ADL) All free functions std::operator<<
  • (ADL) All free functions Y::operator<< (or would, if there were any).

and nothing else.

None of those find a match for argument type Y::A so compilation fails.

When you remove X::operator<<, then the unqualified lookup step finds nothing in X so it looks in the parent namespace, recursively. Then ::operator<< is found and that function goes into the overload set, and compilation succeeds.

To avoid this problem, the usual procedure is to put free overloaded operators of user-defined types into the same namespace as the type was defined, so in this case you would do:

namespace Y
{
    inline std::wostream & operator<<(std::wostream & stream, const A & msg) { .... }
}

and then the ADL step would find this function even though the unqualified lookup step also finds X::operator<<.


In your second example the exact meaning of using namespace Z; is:

During unqualified name lookup, the names appear as if they were declared in the nearest enclosing namespace which contains both the using-directive and the nominated namespace.

The nearest enclosing namespace containing both X and Z is the global namespace, so the names behave as if in the global namespace for the unqualified lookup phase.

Therefore the process is not substantially different to my analysis for your first case, and only X::operator<< is found by unqualified lookup. Again, this would be fixed by including the desired overload in Y so that it is found by ADL.

Upvotes: 1

Macmade
Macmade

Reputation: 54029

This behavior is actually expected in C++, in order to avoid unexpected behaviors introduced by different overloads in different namespaces.

This is called name hiding. You can read a really good answer on the subject here: https://stackoverflow.com/a/1629074/182676

So overloads in different namespaces will hide each other.

You can fix this by making the correct overload visible to the compiler with using:

Y::A a;
using Z::operator<<;
std::wcout << a;

Upvotes: 1

Related Questions