C++ container output stream header file

This header file provides extensions to the iostream << operator so you can pretty print C++ containers. It has a few interesting examples of template metaprogramming and uses some of the more modern C++ (2017) features. The repo code is evolving: https://github.com/adrianfreed/containerostream
//
// Created by AdrianFreed on 9/24/20.
// << overloads for printing containers
// requires C++17
//

#ifndef CONTAINER_O_STREAM_H
#define CONTAINER_O_STREAM_H
#include <iostream>
#include <array>
#include <vector>
#include <set>
#include <unordered_set>
#include <map>
#include <unordered_map>
#include <tuple>
#include <deque>
#include <list>
#include <forward_list>

//Pairs
template<typename TA, typename TB>
auto &operator<<(std::ostream &os, const std::pair<TA, TB> &p) {
    os << '(' << p.first << ',' << p.second << ')';
    return os;
}

//Tuples
// variadic template metaprogram to iterate over the tuple elements
// requires C++17, replace with messier folds if using earlier version
template<size_t I = 0, typename... Tp>
void tuple_element_print(const std::tuple<Tp...> &t, std::ostream &os) {
    os << std::get<I>(t);
    if constexpr(I + 1 != sizeof...(Tp)) {
        os << ", ";
        tuple_element_print<I + 1>(t, os);
    }
}

template<typename ...Types>
auto &operator<<(std::ostream &os, const std::tuple<Types ...> &t) {
    os << "(T ";
    tuple_element_print(t, os);
    os << ")";
    return os;
}

namespace CONTAINERSTREAM {


//Maps
    template<typename C>
    std::ostream &output(std::ostream &os, C &v, const char *first, const char *separator, const char *last) {
        os << first;
        for (auto it = v.cbegin(); it != v.cend(); ++it) {
            os << it->first << ':' << it->second;
            if (std::next(it) != v.cend())
                os << separator;
        }
        os << last;
        return os;
    }
}

template<typename T, typename Tbis>
std::ostream &operator<<(std::ostream &os, const std::unordered_map<T, Tbis> &v) {
    return CONTAINERSTREAM::output(os, v, "[Unorderedmap ", ", ", "]");
}

template<typename T, typename Tbis>
std::ostream &operator<<(std::ostream &os, const std::map<T, Tbis> &v) {
    return CONTAINERSTREAM::output(os, v, "[Map ", ", ", "]");
}

template<typename T, typename Tbis>
std::ostream &operator<<(std::ostream &os, const std::multimap<T, Tbis> &v) {
    return CONTAINERSTREAM::output(os, v, "[Multimap ", ", ", "]");
}

namespace CONTAINERSTREAM {

// The Rest
    template<typename C>
    std::ostream &
    vectorarrayoutput(std::ostream &os, C &v, const char *first, const char *separator, const char *last) {
        os << first;
        for (auto it = v.cbegin(); it != v.cend(); ++it) {
            os << *it;
            if (std::next(it) != v.cend())
                os << separator;
        }
        os << last;
        return os;
    }

}

// There may appear to be a lot of repetition in these functions
// The idea behind that is that you are invited to change the styling of
// each separator and bracket according to your own aesthetics or conventions
template<typename T, std::size_t N>
auto &operator<<(std::ostream &os, const std::array<T, N> &a) {
    return CONTAINERSTREAM::vectorarrayoutput(os, a, "[Array ", ", ", "]");
}

template<typename T>
std::ostream &operator<<(std::ostream &os, const std::vector<T> &v) {
    return CONTAINERSTREAM::vectorarrayoutput(os, v, "[Vector ", ", ", "]");
}

template<typename T>
std::ostream &operator<<(std::ostream &os, const std::set<T> &v) {
    return CONTAINERSTREAM::vectorarrayoutput(os, v, "[Set ", ", ", "]");
}

template<typename T>
std::ostream &operator<<(std::ostream &os, const std::multiset<T> &v) {
    return CONTAINERSTREAM::vectorarrayoutput(os, v, "[Multiset ", ", ", "]");
}

template<typename T>
std::ostream &operator<<(std::ostream &os, const std::list<T> &l) {
    return CONTAINERSTREAM::vectorarrayoutput(os, l, "(List ", ", ", ")");
}

template<typename T>
std::ostream &operator<<(std::ostream &os, const std::forward_list<T> &l) {
    return CONTAINERSTREAM::vectorarrayoutput(os, l, "(ForwardList ", ", ", ")");
}

template<typename T>
std::ostream &operator<<(std::ostream &os, const std::deque<T> &d) {
    return CONTAINERSTREAM::vectorarrayoutput(os, d, "(Deque ", ", ", ")");
}

#endif //TEST_CONTAINER_O_STREAM_H

Comments

Here is a simple test by AdrianFreed