Please take note of the updates at the end of this post.
Update: I have created a public project on GitHub for this library!
Here is a working library, presented as a complete working program, that I just hacked together:
#include <set>
#include <vector>
#include <iostream>
#include <boost/utility/enable_if.hpp>
// Default delimiters
template <class C> struct Delims { static const char *delim[3]; };
template <class C> const char *Delims<C>::delim[3]={"[", ", ", "]"};
// Special delimiters for sets.
template <typename T> struct Delims< std::set<T> > { static const char *delim[3]; };
template <typename T> const char *Delims< std::set<T> >::delim[3]={"{", ", ", "}"};
template <class C> struct IsContainer { enum { value = false }; };
template <typename T> struct IsContainer< std::vector<T> > { enum { value = true }; };
template <typename T> struct IsContainer< std::set<T> > { enum { value = true }; };
template <class C>
typename boost::enable_if<IsContainer<C>, std::ostream&>::type
operator<<(std::ostream & o, const C & x)
{
o << Delims<C>::delim[0];
for (typename C::const_iterator i = x.begin(); i != x.end(); ++i)
{
if (i != x.begin()) o << Delims<C>::delim[1];
o << *i;
}
o << Delims<C>::delim[2];
return o;
}
template <typename T> struct IsChar { enum { value = false }; };
template <> struct IsChar<char> { enum { value = true }; };
template <typename T, int N>
typename boost::disable_if<IsChar<T>, std::ostream&>::type
operator<<(std::ostream & o, const T (&x)[N])
{
o << "[";
for (int i = 0; i != N; ++i)
{
if (i) o << ",";
o << x[i];
}
o << "]";
return o;
}
int main()
{
std::vector<int> i;
i.push_back(23);
i.push_back(34);
std::set<std::string> j;
j.insert("hello");
j.insert("world");
double k[] = { 1.1, 2.2, M_PI, -1.0/123.0 };
std::cout << i << "\n" << j << "\n" << k << "\n";
}
It currently only works with vector
and set
, but can be made to work with most containers, just by expanding on the IsContainer
specializations. I haven't thought much about whether this code is minimal, but I can't immediately think of anything I could strip out as redundant.
EDIT: Just for kicks, I included a version that handles arrays. I had to exclude char arrays to avoid further ambiguities; it might still get into trouble with wchar_t[]
.
Here is my version of implementation done in 2016
Everything in one header, so it's easy to use https://github.com/skident/eos/blob/master/include/eos/io/print.hpp
/*! \file print.hpp
* \brief Useful functions for work with STL containers.
*
* Now it supports generic print for STL containers like: [elem1, elem2, elem3]
* Supported STL conrainers: vector, deque, list, set multiset, unordered_set,
* map, multimap, unordered_map, array
*
* \author Skident
* \date 02.09.2016
* \copyright Skident Inc.
*/
#pragma once
// check is the C++11 or greater available (special hack for MSVC)
#if (defined(_MSC_VER) && __cplusplus >= 199711L) || __cplusplus >= 201103L
#define MODERN_CPP_AVAILABLE 1
#endif
#include <iostream>
#include <sstream>
#include <vector>
#include <deque>
#include <set>
#include <list>
#include <map>
#include <cctype>
#ifdef MODERN_CPP_AVAILABLE
#include <array>
#include <unordered_set>
#include <unordered_map>
#include <forward_list>
#endif
#define dump(value) std::cout << (#value) << ": " << (value) << std::endl
#define BUILD_CONTENT \
std::stringstream ss; \
for (; it != collection.end(); ++it) \
{ \
ss << *it << elem_separator; \
} \
#define BUILD_MAP_CONTENT \
std::stringstream ss; \
for (; it != collection.end(); ++it) \
{ \
ss << it->first \
<< keyval_separator \
<< it->second \
<< elem_separator; \
} \
#define COMPILE_CONTENT \
std::string data = ss.str(); \
if (!data.empty() && !elem_separator.empty()) \
data = data.substr(0, data.rfind(elem_separator)); \
std::string result = first_bracket + data + last_bracket; \
os << result; \
if (needEndl) \
os << std::endl; \
////
///
///
/// Template definitions
///
///
//generic template for classes: deque, list, forward_list, vector
#define VECTOR_AND_CO_TEMPLATE \
template< \
template<class T, \
class Alloc = std::allocator<T> > \
class Container, class Type, class Alloc> \
#define SET_TEMPLATE \
template< \
template<class T, \
class Compare = std::less<T>, \
class Alloc = std::allocator<T> > \
class Container, class T, class Compare, class Alloc> \
#define USET_TEMPLATE \
template< \
template < class Key, \
class Hash = std::hash<Key>, \
class Pred = std::equal_to<Key>, \
class Alloc = std::allocator<Key> \
> \
class Container, class Key, class Hash, class Pred, class Alloc \
> \
#define MAP_TEMPLATE \
template< \
template<class Key, \
class T, \
class Compare = std::less<Key>, \
class Alloc = std::allocator<std::pair<const Key,T> > \
> \
class Container, class Key, \
class Value/*, class Compare, class Alloc*/> \
#define UMAP_TEMPLATE \
template< \
template<class Key, \
class T, \
class Hash = std::hash<Key>, \
class Pred = std::equal_to<Key>, \
class Alloc = std::allocator<std::pair<const Key,T> >\
> \
class Container, class Key, class Value, \
class Hash, class Pred, class Alloc \
> \
#define ARRAY_TEMPLATE \
template< \
template<class T, std::size_t N> \
class Array, class Type, std::size_t Size> \
namespace eos
{
static const std::string default_elem_separator = ", ";
static const std::string default_keyval_separator = " => ";
static const std::string default_first_bracket = "[";
static const std::string default_last_bracket = "]";
//! Prints template Container<T> as in Python
//! Supported containers: vector, deque, list, set, unordered_set(C++11), forward_list(C++11)
//! \param collection which should be printed
//! \param elem_separator the separator which will be inserted between elements of collection
//! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
//! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
template<class Container>
void print( const Container& collection
, const std::string& elem_separator = default_elem_separator
, const std::string& first_bracket = default_first_bracket
, const std::string& last_bracket = default_last_bracket
, std::ostream& os = std::cout
, bool needEndl = true
)
{
typename Container::const_iterator it = collection.begin();
BUILD_CONTENT
COMPILE_CONTENT
}
//! Prints collections with one template argument and allocator as in Python.
//! Supported standard collections: vector, deque, list, forward_list
//! \param collection which should be printed
//! \param elem_separator the separator which will be inserted between elements of collection
//! \param keyval_separator separator between key and value of map. For default it is the '=>'
//! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
//! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
VECTOR_AND_CO_TEMPLATE
void print( const Container<Type>& collection
, const std::string& elem_separator = default_elem_separator
, const std::string& first_bracket = default_first_bracket
, const std::string& last_bracket = default_last_bracket
, std::ostream& os = std::cout
, bool needEndl = true
)
{
typename Container<Type>::const_iterator it = collection.begin();
BUILD_CONTENT
COMPILE_CONTENT
}
//! Prints collections like std:set<T, Compare, Alloc> as in Python
//! \param collection which should be printed
//! \param elem_separator the separator which will be inserted between elements of collection
//! \param keyval_separator separator between key and value of map. For default it is the '=>'
//! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
//! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
SET_TEMPLATE
void print( const Container<T, Compare, Alloc>& collection
, const std::string& elem_separator = default_elem_separator
, const std::string& first_bracket = default_first_bracket
, const std::string& last_bracket = default_last_bracket
, std::ostream& os = std::cout
, bool needEndl = true
)
{
typename Container<T, Compare, Alloc>::const_iterator it = collection.begin();
BUILD_CONTENT
COMPILE_CONTENT
}
//! Prints collections like std:unordered_set<Key, Hash, Pred, Alloc> as in Python
//! \param collection which should be printed
//! \param elem_separator the separator which will be inserted between elements of collection
//! \param keyval_separator separator between key and value of map. For default it is the '=>'
//! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
//! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
USET_TEMPLATE
void print( const Container<Key, Hash, Pred, Alloc>& collection
, const std::string& elem_separator = default_elem_separator
, const std::string& first_bracket = default_first_bracket
, const std::string& last_bracket = default_last_bracket
, std::ostream& os = std::cout
, bool needEndl = true
)
{
typename Container<Key, Hash, Pred, Alloc>::const_iterator it = collection.begin();
BUILD_CONTENT
COMPILE_CONTENT
}
//! Prints collections like std:map<T, U> as in Python
//! supports generic objects of std: map, multimap
//! \param collection which should be printed
//! \param elem_separator the separator which will be inserted between elements of collection
//! \param keyval_separator separator between key and value of map. For default it is the '=>'
//! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
//! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
MAP_TEMPLATE
void print( const Container<Key, Value>& collection
, const std::string& elem_separator = default_elem_separator
, const std::string& keyval_separator = default_keyval_separator
, const std::string& first_bracket = default_first_bracket
, const std::string& last_bracket = default_last_bracket
, std::ostream& os = std::cout
, bool needEndl = true
)
{
typename Container<Key, Value>::const_iterator it = collection.begin();
BUILD_MAP_CONTENT
COMPILE_CONTENT
}
//! Prints classes like std:unordered_map as in Python
//! \param collection which should be printed
//! \param elem_separator the separator which will be inserted between elements of collection
//! \param keyval_separator separator between key and value of map. For default it is the '=>'
//! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
//! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
UMAP_TEMPLATE
void print( const Container<Key, Value, Hash, Pred, Alloc>& collection
, const std::string& elem_separator = default_elem_separator
, const std::string& keyval_separator = default_keyval_separator
, const std::string& first_bracket = default_first_bracket
, const std::string& last_bracket = default_last_bracket
, std::ostream& os = std::cout
, bool needEndl = true
)
{
typename Container<Key, Value, Hash, Pred, Alloc>::const_iterator it = collection.begin();
BUILD_MAP_CONTENT
COMPILE_CONTENT
}
//! Prints collections like std:array<T, Size> as in Python
//! \param collection which should be printed
//! \param elem_separator the separator which will be inserted between elements of collection
//! \param keyval_separator separator between key and value of map. For default it is the '=>'
//! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
//! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
ARRAY_TEMPLATE
void print( const Array<Type, Size>& collection
, const std::string& elem_separator = default_elem_separator
, const std::string& first_bracket = default_first_bracket
, const std::string& last_bracket = default_last_bracket
, std::ostream& os = std::cout
, bool needEndl = true
)
{
typename Array<Type, Size>::const_iterator it = collection.begin();
BUILD_CONTENT
COMPILE_CONTENT
}
//! Removes all whitespaces before data in string.
//! \param str string with data
//! \return string without whitespaces in left part
std::string ltrim(const std::string& str);
//! Removes all whitespaces after data in string
//! \param str string with data
//! \return string without whitespaces in right part
std::string rtrim(const std::string& str);
//! Removes all whitespaces before and after data in string
//! \param str string with data
//! \return string without whitespaces before and after data in string
std::string trim(const std::string& str);
////////////////////////////////////////////////////////////
////////////////////////ostream logic//////////////////////
/// Should be specified for concrete containers
/// because of another types can be suitable
/// for templates, for example templates break
/// the code like this "cout << string("hello") << endl;"
////////////////////////////////////////////////////////////
#define PROCESS_VALUE_COLLECTION(os, collection) \
print( collection, \
default_elem_separator, \
default_first_bracket, \
default_last_bracket, \
os, \
false \
); \
#define PROCESS_KEY_VALUE_COLLECTION(os, collection) \
print( collection, \
default_elem_separator, \
default_keyval_separator, \
default_first_bracket, \
default_last_bracket, \
os, \
false \
); \
///< specialization for vector
template<class T>
std::ostream& operator<<(std::ostream& os, const std::vector<T>& collection)
{
PROCESS_VALUE_COLLECTION(os, collection)
return os;
}
///< specialization for deque
template<class T>
std::ostream& operator<<(std::ostream& os, const std::deque<T>& collection)
{
PROCESS_VALUE_COLLECTION(os, collection)
return os;
}
///< specialization for list
template<class T>
std::ostream& operator<<(std::ostream& os, const std::list<T>& collection)
{
PROCESS_VALUE_COLLECTION(os, collection)
return os;
}
///< specialization for set
template<class T>
std::ostream& operator<<(std::ostream& os, const std::set<T>& collection)
{
PROCESS_VALUE_COLLECTION(os, collection)
return os;
}
///< specialization for multiset
template<class T>
std::ostream& operator<<(std::ostream& os, const std::multiset<T>& collection)
{
PROCESS_VALUE_COLLECTION(os, collection)
return os;
}
#ifdef MODERN_CPP_AVAILABLE
///< specialization for unordered_map
template<class T>
std::ostream& operator<<(std::ostream& os, const std::unordered_set<T>& collection)
{
PROCESS_VALUE_COLLECTION(os, collection)
return os;
}
///< specialization for forward_list
template<class T>
std::ostream& operator<<(std::ostream& os, const std::forward_list<T>& collection)
{
PROCESS_VALUE_COLLECTION(os, collection)
return os;
}
///< specialization for array
template<class T, std::size_t N>
std::ostream& operator<<(std::ostream& os, const std::array<T, N>& collection)
{
PROCESS_VALUE_COLLECTION(os, collection)
return os;
}
#endif
///< specialization for map, multimap
MAP_TEMPLATE
std::ostream& operator<<(std::ostream& os, const Container<Key, Value>& collection)
{
PROCESS_KEY_VALUE_COLLECTION(os, collection)
return os;
}
///< specialization for unordered_map
UMAP_TEMPLATE
std::ostream& operator<<(std::ostream& os, const Container<Key, Value, Hash, Pred, Alloc>& collection)
{
PROCESS_KEY_VALUE_COLLECTION(os, collection)
return os;
}
}
This solution was inspired by Marcelo's solution, with a few changes:
#include <iostream>
#include <iterator>
#include <type_traits>
#include <vector>
#include <algorithm>
// This works similar to ostream_iterator, but doesn't print a delimiter after the final item
template<typename T, typename TChar = char, typename TCharTraits = std::char_traits<TChar> >
class pretty_ostream_iterator : public std::iterator<std::output_iterator_tag, void, void, void, void>
{
public:
typedef TChar char_type;
typedef TCharTraits traits_type;
typedef std::basic_ostream<TChar, TCharTraits> ostream_type;
pretty_ostream_iterator(ostream_type &stream, const char_type *delim = NULL)
: _stream(&stream), _delim(delim), _insertDelim(false)
{
}
pretty_ostream_iterator<T, TChar, TCharTraits>& operator=(const T &value)
{
if( _delim != NULL )
{
// Don't insert a delimiter if this is the first time the function is called
if( _insertDelim )
(*_stream) << _delim;
else
_insertDelim = true;
}
(*_stream) << value;
return *this;
}
pretty_ostream_iterator<T, TChar, TCharTraits>& operator*()
{
return *this;
}
pretty_ostream_iterator<T, TChar, TCharTraits>& operator++()
{
return *this;
}
pretty_ostream_iterator<T, TChar, TCharTraits>& operator++(int)
{
return *this;
}
private:
ostream_type *_stream;
const char_type *_delim;
bool _insertDelim;
};
#if _MSC_VER >= 1400
// Declare pretty_ostream_iterator as checked
template<typename T, typename TChar, typename TCharTraits>
struct std::_Is_checked_helper<pretty_ostream_iterator<T, TChar, TCharTraits> > : public std::tr1::true_type
{
};
#endif // _MSC_VER >= 1400
namespace std
{
// Pre-declarations of container types so we don't actually have to include the relevant headers if not needed, speeding up compilation time.
// These aren't necessary if you do actually include the headers.
template<typename T, typename TAllocator> class vector;
template<typename T, typename TAllocator> class list;
template<typename T, typename TTraits, typename TAllocator> class set;
template<typename TKey, typename TValue, typename TTraits, typename TAllocator> class map;
}
// Basic is_container template; specialize to derive from std::true_type for all desired container types
template<typename T> struct is_container : public std::false_type { };
// Mark vector as a container
template<typename T, typename TAllocator> struct is_container<std::vector<T, TAllocator> > : public std::true_type { };
// Mark list as a container
template<typename T, typename TAllocator> struct is_container<std::list<T, TAllocator> > : public std::true_type { };
// Mark set as a container
template<typename T, typename TTraits, typename TAllocator> struct is_container<std::set<T, TTraits, TAllocator> > : public std::true_type { };
// Mark map as a container
template<typename TKey, typename TValue, typename TTraits, typename TAllocator> struct is_container<std::map<TKey, TValue, TTraits, TAllocator> > : public std::true_type { };
// Holds the delimiter values for a specific character type
template<typename TChar>
struct delimiters_values
{
typedef TChar char_type;
const TChar *prefix;
const TChar *delimiter;
const TChar *postfix;
};
// Defines the delimiter values for a specific container and character type
template<typename T, typename TChar>
struct delimiters
{
static const delimiters_values<TChar> values;
};
// Default delimiters
template<typename T> struct delimiters<T, char> { static const delimiters_values<char> values; };
template<typename T> const delimiters_values<char> delimiters<T, char>::values = { "{ ", ", ", " }" };
template<typename T> struct delimiters<T, wchar_t> { static const delimiters_values<wchar_t> values; };
template<typename T> const delimiters_values<wchar_t> delimiters<T, wchar_t>::values = { L"{ ", L", ", L" }" };
// Delimiters for set
template<typename T, typename TTraits, typename TAllocator> struct delimiters<std::set<T, TTraits, TAllocator>, char> { static const delimiters_values<char> values; };
template<typename T, typename TTraits, typename TAllocator> const delimiters_values<char> delimiters<std::set<T, TTraits, TAllocator>, char>::values = { "[ ", ", ", " ]" };
template<typename T, typename TTraits, typename TAllocator> struct delimiters<std::set<T, TTraits, TAllocator>, wchar_t> { static const delimiters_values<wchar_t> values; };
template<typename T, typename TTraits, typename TAllocator> const delimiters_values<wchar_t> delimiters<std::set<T, TTraits, TAllocator>, wchar_t>::values = { L"[ ", L", ", L" ]" };
// Delimiters for pair
template<typename T1, typename T2> struct delimiters<std::pair<T1, T2>, char> { static const delimiters_values<char> values; };
template<typename T1, typename T2> const delimiters_values<char> delimiters<std::pair<T1, T2>, char>::values = { "(", ", ", ")" };
template<typename T1, typename T2> struct delimiters<std::pair<T1, T2>, wchar_t> { static const delimiters_values<wchar_t> values; };
template<typename T1, typename T2> const delimiters_values<wchar_t> delimiters<std::pair<T1, T2>, wchar_t>::values = { L"(", L", ", L")" };
// Functor to print containers. You can use this directly if you want to specificy a non-default delimiters type.
template<typename T, typename TChar = char, typename TCharTraits = std::char_traits<TChar>, typename TDelimiters = delimiters<T, TChar> >
struct print_container_helper
{
typedef TChar char_type;
typedef TDelimiters delimiters_type;
typedef std::basic_ostream<TChar, TCharTraits>& ostream_type;
print_container_helper(const T &container)
: _container(&container)
{
}
void operator()(ostream_type &stream) const
{
if( delimiters_type::values.prefix != NULL )
stream << delimiters_type::values.prefix;
std::copy(_container->begin(), _container->end(), pretty_ostream_iterator<typename T::value_type, TChar, TCharTraits>(stream, delimiters_type::values.delimiter));
if( delimiters_type::values.postfix != NULL )
stream << delimiters_type::values.postfix;
}
private:
const T *_container;
};
// Prints a print_container_helper to the specified stream.
template<typename T, typename TChar, typename TCharTraits, typename TDelimiters>
std::basic_ostream<TChar, TCharTraits>& operator<<(std::basic_ostream<TChar, TCharTraits> &stream, const print_container_helper<T, TChar, TDelimiters> &helper)
{
helper(stream);
return stream;
}
// Prints a container to the stream using default delimiters
template<typename T, typename TChar, typename TCharTraits>
typename std::enable_if<is_container<T>::value, std::basic_ostream<TChar, TCharTraits>&>::type
operator<<(std::basic_ostream<TChar, TCharTraits> &stream, const T &container)
{
stream << print_container_helper<T, TChar, TCharTraits>(container);
return stream;
}
// Prints a pair to the stream using delimiters from delimiters<std::pair<T1, T2>>.
template<typename T1, typename T2, typename TChar, typename TCharTraits>
std::basic_ostream<TChar, TCharTraits>& operator<<(std::basic_ostream<TChar, TCharTraits> &stream, const std::pair<T1, T2> &value)
{
if( delimiters<std::pair<T1, T2>, TChar>::values.prefix != NULL )
stream << delimiters<std::pair<T1, T2>, TChar>::values.prefix;
stream << value.first;
if( delimiters<std::pair<T1, T2>, TChar>::values.delimiter != NULL )
stream << delimiters<std::pair<T1, T2>, TChar>::values.delimiter;
stream << value.second;
if( delimiters<std::pair<T1, T2>, TChar>::values.postfix != NULL )
stream << delimiters<std::pair<T1, T2>, TChar>::values.postfix;
return stream;
}
// Used by the sample below to generate some values
struct fibonacci
{
fibonacci() : f1(0), f2(1) { }
int operator()()
{
int r = f1 + f2;
f1 = f2;
f2 = r;
return f1;
}
private:
int f1;
int f2;
};
int main()
{
std::vector<int> v;
std::generate_n(std::back_inserter(v), 10, fibonacci());
std::cout << v << std::endl;
// Example of using pretty_ostream_iterator directly
std::generate_n(pretty_ostream_iterator<int>(std::cout, ";"), 20, fibonacci());
std::cout << std::endl;
}
Like Marcelo's version, it uses an is_container type trait that must be specialized for all containers that are to be supported. It may be possible to use a trait to check for value_type
, const_iterator
, begin()
/end()
, but I'm not sure I'd recommend that since it might match things that match those criteria but aren't actually containers, like std::basic_string
. Also like Marcelo's version, it uses templates that can be specialized to specify the delimiters to use.
The major difference is that I've built my version around a pretty_ostream_iterator
, which works similar to the std::ostream_iterator
but doesn't print a delimiter after the last item. Formatting the containers is done by the print_container_helper
, which can be used directly to print containers without an is_container trait, or to specify a different delimiters type.
I've also defined is_container and delimiters so it will work for containers with non-standard predicates or allocators, and for both char and wchar_t. The operator<< function itself is also defined to work with both char and wchar_t streams.
Finally, I've used std::enable_if
, which is available as part of C++0x, and works in Visual C++ 2010 and g++ 4.3 (needs the -std=c++0x flag) and later. This way there is no dependency on Boost.
The goal here is to use ADL to do customization of how we pretty print.
You pass in a formatter tag, and override 4 functions (before, after, between and descend) in the tag's namespace. This changes how the formatter prints 'adornments' when iterating over containers.
A default formatter that does {(a->b),(c->d)}
for maps, (a,b,c)
for tupleoids, "hello"
for strings, [x,y,z]
for everything else included.
It should "just work" with 3rd party iterable types (and treat them like "everything else").
If you want custom adornments for your 3rd party iterables, simply create your own tag. It will take a bit of work to handle map descent (you need to overload pretty_print_descend( your_tag
to return pretty_print::decorator::map_magic_tag<your_tag>
). Maybe there is a cleaner way to do this, not sure.
A little library to detect iterability, and tuple-ness:
namespace details {
using std::begin; using std::end;
template<class T, class=void>
struct is_iterable_test:std::false_type{};
template<class T>
struct is_iterable_test<T,
decltype((void)(
(void)(begin(std::declval<T>())==end(std::declval<T>()))
, ((void)(std::next(begin(std::declval<T>()))))
, ((void)(*begin(std::declval<T>())))
, 1
))
>:std::true_type{};
template<class T>struct is_tupleoid:std::false_type{};
template<class...Ts>struct is_tupleoid<std::tuple<Ts...>>:std::true_type{};
template<class...Ts>struct is_tupleoid<std::pair<Ts...>>:std::true_type{};
// template<class T, size_t N>struct is_tupleoid<std::array<T,N>>:std::true_type{}; // complete, but problematic
}
template<class T>struct is_iterable:details::is_iterable_test<std::decay_t<T>>{};
template<class T, std::size_t N>struct is_iterable<T(&)[N]>:std::true_type{}; // bypass decay
template<class T>struct is_tupleoid:details::is_tupleoid<std::decay_t<T>>{};
template<class T>struct is_visitable:std::integral_constant<bool, is_iterable<T>{}||is_tupleoid<T>{}> {};
A library that lets us visit the contents of an iterable or tuple type object:
template<class C, class F>
std::enable_if_t<is_iterable<C>{}> visit_first(C&& c, F&& f) {
using std::begin; using std::end;
auto&& b = begin(c);
auto&& e = end(c);
if (b==e)
return;
std::forward<F>(f)(*b);
}
template<class C, class F>
std::enable_if_t<is_iterable<C>{}> visit_all_but_first(C&& c, F&& f) {
using std::begin; using std::end;
auto it = begin(c);
auto&& e = end(c);
if (it==e)
return;
it = std::next(it);
for( ; it!=e; it = std::next(it) ) {
f(*it);
}
}
namespace details {
template<class Tup, class F>
void visit_first( std::index_sequence<>, Tup&&, F&& ) {}
template<size_t... Is, class Tup, class F>
void visit_first( std::index_sequence<0,Is...>, Tup&& tup, F&& f ) {
std::forward<F>(f)( std::get<0>( std::forward<Tup>(tup) ) );
}
template<class Tup, class F>
void visit_all_but_first( std::index_sequence<>, Tup&&, F&& ) {}
template<size_t... Is,class Tup, class F>
void visit_all_but_first( std::index_sequence<0,Is...>, Tup&& tup, F&& f ) {
int unused[] = {0,((void)(
f( std::get<Is>(std::forward<Tup>(tup)) )
),0)...};
(void)(unused);
}
}
template<class Tup, class F>
std::enable_if_t<is_tupleoid<Tup>{}> visit_first(Tup&& tup, F&& f) {
details::visit_first( std::make_index_sequence< std::tuple_size<std::decay_t<Tup>>{} >{}, std::forward<Tup>(tup), std::forward<F>(f) );
}
template<class Tup, class F>
std::enable_if_t<is_tupleoid<Tup>{}> visit_all_but_first(Tup&& tup, F&& f) {
details::visit_all_but_first( std::make_index_sequence< std::tuple_size<std::decay_t<Tup>>{} >{}, std::forward<Tup>(tup), std::forward<F>(f) );
}
A pretty printing library:
namespace pretty_print {
namespace decorator {
struct default_tag {};
template<class Old>
struct map_magic_tag:Old {}; // magic for maps
// Maps get {}s. Write trait `is_associative` to generalize:
template<class CharT, class Traits, class...Xs >
void pretty_print_before( default_tag, std::basic_ostream<CharT, Traits>& s, std::map<Xs...> const& ) {
s << CharT('{');
}
template<class CharT, class Traits, class...Xs >
void pretty_print_after( default_tag, std::basic_ostream<CharT, Traits>& s, std::map<Xs...> const& ) {
s << CharT('}');
}
// tuples and pairs get ():
template<class CharT, class Traits, class Tup >
std::enable_if_t<is_tupleoid<Tup>{}> pretty_print_before( default_tag, std::basic_ostream<CharT, Traits>& s, Tup const& ) {
s << CharT('(');
}
template<class CharT, class Traits, class Tup >
std::enable_if_t<is_tupleoid<Tup>{}> pretty_print_after( default_tag, std::basic_ostream<CharT, Traits>& s, Tup const& ) {
s << CharT(')');
}
// strings with the same character type get ""s:
template<class CharT, class Traits, class...Xs >
void pretty_print_before( default_tag, std::basic_ostream<CharT, Traits>& s, std::basic_string<CharT, Xs...> const& ) {
s << CharT('"');
}
template<class CharT, class Traits, class...Xs >
void pretty_print_after( default_tag, std::basic_ostream<CharT, Traits>& s, std::basic_string<CharT, Xs...> const& ) {
s << CharT('"');
}
// and pack the characters together:
template<class CharT, class Traits, class...Xs >
void pretty_print_between( default_tag, std::basic_ostream<CharT, Traits>&, std::basic_string<CharT, Xs...> const& ) {}
// map magic. When iterating over the contents of a map, use the map_magic_tag:
template<class...Xs>
map_magic_tag<default_tag> pretty_print_descend( default_tag, std::map<Xs...> const& ) {
return {};
}
template<class old_tag, class C>
old_tag pretty_print_descend( map_magic_tag<old_tag>, C const& ) {
return {};
}
// When printing a pair immediately within a map, use -> as a separator:
template<class old_tag, class CharT, class Traits, class...Xs >
void pretty_print_between( map_magic_tag<old_tag>, std::basic_ostream<CharT, Traits>& s, std::pair<Xs...> const& ) {
s << CharT('-') << CharT('>');
}
}
// default behavior:
template<class CharT, class Traits, class Tag, class Container >
void pretty_print_before( Tag const&, std::basic_ostream<CharT, Traits>& s, Container const& ) {
s << CharT('[');
}
template<class CharT, class Traits, class Tag, class Container >
void pretty_print_after( Tag const&, std::basic_ostream<CharT, Traits>& s, Container const& ) {
s << CharT(']');
}
template<class CharT, class Traits, class Tag, class Container >
void pretty_print_between( Tag const&, std::basic_ostream<CharT, Traits>& s, Container const& ) {
s << CharT(',');
}
template<class Tag, class Container>
Tag&& pretty_print_descend( Tag&& tag, Container const& ) {
return std::forward<Tag>(tag);
}
// print things by default by using <<:
template<class Tag=decorator::default_tag, class Scalar, class CharT, class Traits>
std::enable_if_t<!is_visitable<Scalar>{}> print( std::basic_ostream<CharT, Traits>& os, Scalar&& scalar, Tag&&=Tag{} ) {
os << std::forward<Scalar>(scalar);
}
// for anything visitable (see above), use the pretty print algorithm:
template<class Tag=decorator::default_tag, class C, class CharT, class Traits>
std::enable_if_t<is_visitable<C>{}> print( std::basic_ostream<CharT, Traits>& os, C&& c, Tag&& tag=Tag{} ) {
pretty_print_before( std::forward<Tag>(tag), os, std::forward<C>(c) );
visit_first( c, [&](auto&& elem) {
print( os, std::forward<decltype(elem)>(elem), pretty_print_descend( std::forward<Tag>(tag), std::forward<C>(c) ) );
});
visit_all_but_first( c, [&](auto&& elem) {
pretty_print_between( std::forward<Tag>(tag), os, std::forward<C>(c) );
print( os, std::forward<decltype(elem)>(elem), pretty_print_descend( std::forward<Tag>(tag), std::forward<C>(c) ) );
});
pretty_print_after( std::forward<Tag>(tag), os, std::forward<C>(c) );
}
}
Test code:
int main() {
std::vector<int> x = {1,2,3};
pretty_print::print( std::cout, x );
std::cout << "\n";
std::map< std::string, int > m;
m["hello"] = 3;
m["world"] = 42;
pretty_print::print( std::cout, m );
std::cout << "\n";
}
live example
This does use C++14 features (some _t
aliases, and auto&&
lambdas), but none are essential.