Here is a (very distilled) use case and code example (sorry if it doesn't look too minimal, I couldn't figure out what else to exclude. The complete 'just compile' code can be found here: https://gcc.godbolt.org/z/5GMEGKG7T)
I want to have a "special" output stream, which, for certain user types, does special treatment during stream processing.
This special stream should be able to stream a "special" type with "special" treatment, as well as any other type which could be streamed through conventional streams - in the latter case, we simply use the conventional stream directly
struct Type { }; // Special type
struct Stream { // Special stream
// In this distilled example, we could achieve the same result
// by overriding << for ostream and Type, but the actual use case is wider
// and requires usage of the special stream type
// For illustration purposes only, we use this overload to cout "T!" for Types
Stream& stream(Type t) { std::cout << "T!"; return *this;}
// For any type for which cout << T{} is a valid operation, use cout for streaming
template<class T>
auto stream(const T& t) -> decltype(std::cout << std::declval<T>(), std::declval<Stream&>())
{
std::cout << t; return *this;
}
};
// Consumes every T for which Stream.stream is defined - those would be Type
// as well as anything else which can be sent to cout
template<class T>
auto operator<<(Stream& s, const T& t) -> decltype(s.stream(t))
{
s.stream(t);
return s;
}
This code works as expected:
void foo()
{
Stream stream;
stream << 10 << Type{};
}
Now I want to define a different struct, which has a member of Type in it, and a streaming operator for the same struct:
struct Compound {
Type t;
int i;
};
// We want the `<<` operator of the struct templated, because we want it to work with
// any possible stream-like object out there
template<class S>
S& operator<<(S& s, Compound comp)
{
s << comp.t << " " << comp.i;
return s;
};
Unfortunately, this doesn't work well. An attempt to use this << operator:
void bar(Compound comp)
{
Stream s;
s << comp;
}
Triggers an ambiguity:
error: ambiguous overload for 'operator<<' (operand types are 'Stream' and 'Compound')
note: candidate: 'decltype (s.Stream::stream(t)) operator<<(Stream&, const T&)
note: candidate: 'S& operator<<(S&, Compound) [with S = Stream]'
That makes sense, since after defining templated operator for << for Compound, it could be streamed via cout, as well as through this new operator!
I can certainly work aorund this by making << for Compound non-templated, and instead use the special Stream as a stream argument - but I'd like to avoid it.
Is there a way I can preserve the templated << for Compound and still avoid the ambiguity?