Reputation: 17324
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
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:
printer
definition is processed,op<<
s are processed,op<<(foo)
and finds itop<<(foo)
printer
is needed and foundprinter
op<<(bar)
is neededprinter
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
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 :
std::ostream (std)
.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