I tend to prefer operator overloading. It works really well coupled with some "decorators".
First a range adapter, that we'll use for any container
template <typename It>
struct Range
{
Range(It b, It e): begin(b), end(e) {}
It begin; It end;
};
template <typename It>
Range<It> MakeRange(It b, It e) { return Range<It>(b,e); }
template <typename C>
Range<typename C::const_iterator> MakeRange(C const& c) {
return Range<typename C::const_iterator>(c.cbegin(), c.cend());
}
template <typename Stream, typename It>
Stream& operator<<(Stream& out, Range<It> range) {
if (range.begin == range.end) { return out; }
out << *range.begin;
for (++range.begin; range.begin != range.end; ++range.begin) {
out << *range.begin;
}
return out;
}
Next a Decorator for pairs printing (either tuple-mode or dictionary mode):
template <typename Stream>
class StreamPairDict
{
public:
StreamPairDict(Stream& b): base(b) {}
template <typename T, typename U>
StreamPairDict& append(std::pair<T,U> const& p) {
base << p.first << ": " << p.second;
return *this;
}
template <typename Other>
StreamPairDict& append(Other const& o) {
base << o; return *this;
}
private:
Stream& base;
};
template <typename S, typename T>
StreamPairDict<S>& operator<<(StreamPairDict<S>& stream, T const& t) {
return s.append(t);
}
template <typename S, typename Stream>
StreamPairDict<S> MakeStreamPairDict(Stream& s) {
return StreamPairDict<S>(s);
}
And finally, a general decorator, to trigger ADL without invading the std
namespace:
template <typename S>
class Stream
{
public:
typedef S Base;
Stream(Base& b): base(b) {}
template <typename T>
Stream& append(T const& t) { base << t; return *this; }
private:
Base& base;
};
template <typename S>
Stream<S>& operator<<(Stream<S>& s, bool b) { return s.append(b); }
template <typename S, typename Num>
typename std::enable_if< std::is_arithmetic<Num>::value, Stream<S>&>::type
operator<<(Stream<S>& s, Num n) { return s.append(n); }
template <typename S>
Stream<S>& operator<<(Stream<S>& s, char const* string) {
return s.append(string);
}
template <typename S>
Stream<S>& operator<<(Stream<S>& s, std::string const& string) {
return s.append(string);
}
/// many containers in the STL !!!
template <typename S, typename K, typename V, typename L, typename A>
Stream<S>& operator<<(Stream<S>& s, std::map<K,V,L,A> const& map) {
return MakeStreamPairDict(s) << '{' << MakeRange(map) << '}';
}
template <typename S, typename K, typename V, typename L, typename A>
Stream<S>& operator<<(Stream<S>& s, std::multimap<K,V,L,A> const& map) {
return MakeStreamPairDict(s) << '{' << MakeRange(map) << '}';
}
template <typename S, typename K, typename V, typename H, typename A>
Stream<S>& operator<<(Stream<S>& s, std::unordered_map<K,V,H,A> const& map) {
return MakeStreamPairDict(s) << '{' << MakeRange(map) << '}';
}
template <typename S, typename K, typename V, typename H, typename A>
Stream<S>& operator<<(Stream<S>& s, std::unordered_multimap<K,V,H,A> const& map) {
return MakeStreamPairDict(s) << '{' << MakeRange(map) << '}';
}
And it's used easily:
int main() {
std::map<int, std::map<char, Custom> > myMap;
Stream(std::cout) << myMap << '\n';
}
will work if there is a std::ostream& operator<<(std::ostream&, Custom const&)
.