We have dozens of string-like classes spread across dozens of namespaces, but they each have a similar structure:
namespace N::N1 {
struct S1 {
std::string_view sv() const;
};
}
We'd like to write a template operator<< to insert these into ostreams, something like
template<typename T>
requires requires(const T& t) {
{ t.sv() } -> std::same_as<std::string_view>;
}
std::ostream& operator<<(std::ostream& os, const T& t) {
os << t.sv();
return os;
}
In order for this function template to be found by ADL, it has to either be in std::
or a copy in each of the dozens of N::N1
namespaces. I can't put it in namespace N
as ADL only looks at the innermost enclosing namespace.
Is it legal to add this function template to std::
namespace? cppreference is not clear (to me) on this point. The compilers I've tried all seem to do the right thing in any case.
(C++20 if it matters)
As already pointed out, don't add your operator<<
to std
. Also don't put it in the global namespace either, that's not going to help any generic code that might want to stream your types.
There's a few options here.
The obvious one is just to use a macro to define the stream operator as a hidden friend to inject it into your type. This is very straightforward and will not have any issues or ambiguities because you're defining exactly the stream operator for your type. This obviously works:
namespace N1 {
struct S1 {
auto sv() const -> std::string_view { return "s1"; }
STREAM_SV(S1)
};
}
A different one is to define that exact stream operator in a distinct namespace. That's not going to help ADL find it... unless you bring it in. Which you can:
namespace ops {
template<typename T>
requires requires(const T& t) {
{ t.sv() } -> std::same_as<std::string_view>;
}
std::ostream& operator<<(std::ostream& os, const T& t) {
os << t.sv();
return os;
}
}
namespace N1 {
struct S1 { auto sv() const -> std::string_view { return "s1"; } };
using ops::operator<<;
}
namespace N2 {
struct S2 { auto sv() const -> std::string_view { return "s2"; } };
using ops::operator<<;
}
int main() {
std::cout << N1::S1() << '\n'; // ok
std::cout << N2::S2() << '\n'; // ok
}
A similar approach is to rely on the fact that ADL also considers namespaces of base classes, so you could add an empty ops::empty
type such that this also just works (at the cost of introducing another base class, which may either be actively fine or extremely undesirable):
namespace N1 {
struct S1 : ops::empty {
auto sv() const -> std::string_view { return "s1"; }
};
}