jozxyqk
jozxyqk

Reputation: 17324

no match for operator<<(std::ostream, ns::Type) when in the global namespace

Can someone explain why the << operator cannot be found when it's in the global namespace? Also, why might it be a problem if it could be?

#include <ostream>
#include <iostream>

namespace ns {
struct Bar { int x; };
struct Foo { Bar bar; };
};

// Why does this fix things
#if 0
inline std::ostream& operator<<(std::ostream& os, const ns::Bar& bar);
inline std::ostream& operator<<(std::ostream& os, const ns::Foo& foo);
#endif

template<class T>
std::ostream& printer(std::ostream& os, const T& obj)
{
    os << obj; // error: no match for 'operator<<'
    return os;
}

// I do not own 'ns' but I want to make a generic printer for it
// Wrapping this in 'namespace ns {...}' is the solution, but why?
inline std::ostream& operator<<(std::ostream& os, const ns::Bar& bar)
{
    return printer(os, bar.x);
}

inline std::ostream& operator<<(std::ostream& os, const ns::Foo& foo)
{
    return printer(os, foo.bar);
}

void test()
{
    ns::Foo foo;
    std::cout << foo;
}

https://godbolt.org/z/8bf1EaWPq

I recently hit this after upgrading my OS, which is now on gcc-13 (probably coming from gcc-11). There were no errors before.

At first this might look like an include order problem, but the operator is defined by the time the template is instantiated.

Upvotes: -2

Views: 71

Answers (2)

Rud48
Rud48

Reputation: 1059

In test(), you try to output foo.bar using printer, which needs an operator<< to output bar. Where printer is defined, there is no output operator for bar, which is defined after' printer. Everything works if you forward declare printer and define it after the output operators.

Update:

The sequence of actions:

  1. Template printer definition is processed,
  2. The two op<<s are processed,
  3. Test is processed
  4. The compiler looks for op<<(foo) and finds it
  5. In op<<(foo) printer is needed and found
  6. In printer op<<(bar) is needed
  7. At 1. printer the compiler doesn't know about op<<(bar so emits an error.

If the #if 0 block is enabled, it creates a forward declaration of op<<(bar). Now the compiler knows op<<(bar) exists in step 6. No error is generated.

Why it worked with MSVC and GCC is unknowable. IMHO they were wrong.

:End Update (Which changes nothing from the earlier version except an explanation)

#include <iostream>
#include <ostream>

namespace ns {
struct Bar {
    int x;
};
struct Foo {
    Bar bar;
};
};  // namespace ns

// Why does this fix things
#if 0
inline std::ostream& operator<<(std::ostream& os, const ns::Bar& bar);
inline std::ostream& operator<<(std::ostream& os, const ns::Foo& foo);
#endif

template <class T>
std::ostream& printer(std::ostream& os, const T& obj);

// I do not own 'ns' but I want to make a generic printer for it
// Wrapping this in 'namespace ns {...}' is the solution, but why?
inline std::ostream& operator<<(std::ostream& os, const ns::Bar& bar) {
    return printer(os, bar);
}

inline std::ostream& operator<<(std::ostream& os, const ns::Foo& foo) {
    return printer(os, foo.bar);
}
template <class T>
std::ostream& printer(std::ostream& os, const T& obj) {
    os << obj;  // error: no match for 'operator<<'
    return os;
}

void test() {
    ns::Foo foo;
    std::cout << foo;
}

Upvotes: -1

Bipin B
Bipin B

Reputation: 316

This is because of argument-dependent lookup (ADL) does not look into the global namespace, only into the namespace of the types involved. When os << obj; is called inside printer, the compiler tries to find an appropriate operator<< in :

  1. The global namespace.
  2. The namespace of std::ostream (std).
  3. The namespace of T (which is ns for ns::Bar and ns::Foo).

But because the operator<< definitions are in the global namespace, ADL does not look there because ns::Foo and ns::Bar are from ns, and ADL prioritizes looking inside the ns namespace.

Solution:

Define operator<< inside namespace ns

    namespace ns 
    {
      inline std::ostream& operator<<(std::ostream& os, const Bar& bar) {
        return printer(os, bar.x);
      }

     inline std::ostream& operator<<(std::ostream& os, const Foo& foo) {
        return printer(os, foo.bar);
      }
   }

Why does MSVC accept it while GCC/Clang reject it?

MSVC is more permissive in some ADL cases and sometimes searches additional namespaces. GCC is stricter and follow the standard more rigorously, which is why your code fails there. Maybe in other words, got stricter nowadays.

Why did this work in GCC 11 but break in GCC 13?

Newer versions of GCC may have improved conformance with C++ lookup rules, leading to stricter ADL behavior. GCC 11 might have allowed an implicit lookup from the global namespace, but GCC 13 follows stricter rules.

Upvotes: 5

Related Questions