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
#include "container_o_stream.h" int main() { std::list l{1, 2, 3, 5, 5}; std::cout << l << "\n"; std::forward_list fl{1, 3, 3, 335, 5}; std::cout << fl << "\n"; std::deque d{1, 83, 3, 335, 5}; std::cout << d << "\n"; std::tuple pa{"hi", 23, 1.1, std::pair(13, "bye")}; std::cout << pa << "\n"; std::vector vec{4, 2, 17, 14, 15}; std::cout << vec << "\n"; std::array ar{1, 4, 2, 17, 11, 15}; std::cout << ar << "\n"; std::set set{1, 4, 2, 17, 17, 11, 15}; std::cout << set << "\n"; std::multiset mset{2, 4, 2, 17, 17, 11, 15}; std::cout << mset << "\n"; std::map<char, int> map{{'a', 1}, {'b', 2}}; std::cout << map << "\n"; std::unordered_map<char, int> umap{{'a', 4}, {'5', 2}}; std::cout << umap << "\n"; std::multimap<char, int> mmap{{'a', 4}, {'b', 2}, {'a', 5}}; std::cout << mmap << "\n"; std::array data{std::pair(1, 3), std::pair(3, 4)}; std::cout << data; return 0; }