Printing Standard Library Sequence and Associative Containers in C++ 2011

In my last post, I wrote about printing Standard Library Sequence Containers (i.e. std::deque, std::list, and std::vector) in C++ 1998/2003 by matching two different patterns: C< T, A< T > > … or more simply, C< T, A > … two type parameters … where “C” stood for container, “T” stood for type, and “A” stood for allocator.

In C++ 2011, there are a few more Standard Library Sequence Containers, specifically std::array and std::forward_list. While std::forward_list matches those patterns, std::array does not … so we’ll need to address that.

Additionally, the Standard Library Associative Containers (i.e. std::map, std::multimap, std::set, and std::multiset) use larger patterns. For instance, if we continue with the previous letters and use “K” for key and “P” for comparison, then std::map would have the following pattern: C< K, T, P< K >, A< std::pair< K, T > > > … or more simply, C< K, T, P, A > … four type parameters. Moreover, std::set would have the following pattern: C< K, P< K >, A< K > > … or more simply, C< K, P, A > … three type parameters.

In C++ 2011, there are also a few more associative containers … the Standard Library Unordered Associative Containers (i.e. std::unordered_map, std::unordered_multimap, std::unordered_set, and std::unordered_multiset). These unordered associative containers are just the hash table versions of associative containers; therefore, they all have the same pattern of their associative container counterparts.

Regardless of C++ version, std::map and std::multimap use std::pair to get things done; hence, std::pair will also need to be addressed … and while we’re at it, we might as well as address std::tuple.

Now for the variadic templates: In C++ 2011, we can use a template parameter pack. Per 14.5.3[temp.variadic]/1 of the C++ 2011 Standard, a template parameter pack is a template parameter that accepts zero or more template arguments. This is exactly what we need to capture standard containers with two, three, or four type parameters.

There is only one thing: std::string is very similar to std::set. Since std::string is a typedef for std::basic_string< char, std::char_traits< char >, std::allocator< char > >, then std::string has an identical template pattern to std::set (i.e. C< K, P< K >, A< K > > … or more simply, C< K, P, A > … where C = std::basic_string, K = char, P = std::char_traits, and A = std:allocator. If we provide an explicit operator << for std::string, then there shouldn’t be any ambiguity.

Per 14.1[temp.param]/11 of the C++ 2011 Standard, a template parameter pack of a function template shall not be followed by another template parameter unless that template parameter can be deduced or has a default argument. The example from 14.1[temp.param]/11 includes the following lines:

// U cannot be deduced or specified
template<class... Tclass... Uvoid f() { }
template<class... Tclass Uvoid g() { }

Moreover, if a template-parameter of a primary class template or alias template is a template parameter pack, it shall be the last template-parameter. Because of this, creating a single function template that can capture a variety of nested class templates of any depth can get a tad tricky as we have to fight the Standard, so let’s just stick with matching the C< T, A >, C< K, P, A >, and the C< K, T, P, A > versions of the standard containers … and to make this all a little more “C++ 2011”-ish, let’s use auto, initializer lists, std::begin, and std::end in our code. So, if we have the following main.cpp:

#include <iostream>
#include <string>
#include <array>
#include <deque>
#include <forward_list>
#include <list>
#include <vector>
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <queue>
#include <stack>
#include <tuple>

template < typename C >
inline void write ( std::ostream & oC const & c ) {
    auto i = std::begin( c );
    auto end = std::end( c );
    o << '{';
    if ( i != end ) {
        o << ' ' << *i;
        for ( ++i; i != end; ++i )
            o << ", " << *i;
        o << ' ';
    }
    o << '}';
}

template < template < typenametypenametypename ... > class Ctypename T1typename T2typename ... T_PACK >
inline std::ostream & operator << ( std::ostream & oCT1T2T_PACK ... > const & c ) {
    write( oc );
    return o;
}

template < typename T, size_t N >
inline std::ostream & operator << ( std::ostream & o, std::arrayT, N > const & a ) {
    o << '{';
    write( oa );
    o << '}';
    return o;
}

template < typename Ftypename S >
inline std::ostream & operator << ( std::ostream & o, std::pairFS > const & p ) {
    o << "{ " << std::get< 0 >( p ) << ", " << std::get< 1 >( p ) << " }";
    return o;
}

inline std::ostream & operator << ( std::ostream & o, std::string const & s ) {
    auto i = s.begin();
    auto end = s.end();
    for ( ; i != end; ++i )
        o << *i;
    return o;
}

template < template < typenametypenametypename ... > class Atypename Ttypename Ctypename ... P_PACK >
struct std__adaptor : public ATCP_PACK ... > {
    friend inline std::ostream & operator << ( std::ostream & ostd__adaptorATCP_PACK ... > const & a ) {
        o << a.ATCP_PACK ... >::c;
        return o;
    }
};

template < typename Ttypename C >
inline std::ostream & operator << ( std::ostream & o, std::queueTC > const & q ) {
    o << static_caststd__adaptor< std::queueTC > const & >( q );
    return o;
}

template < typename Ttypename Ctypename P >
inline std::ostream & operator << ( std::ostream & o, std::priority_queueTCP > const & p ) {
    o << static_caststd__adaptor< std::priority_queueTCP > const & >( p );
    return o;
}

template < typename Ttypename C >
inline std::ostream & operator << ( std::ostream & o, std::stackTC > const & s ) {
    o << static_caststd__adaptor< std::stackTC > const & >( s );
    return o;
}

template < size_t i, size_t n, typename ... T_PACK >
struct std__tuple {
    static inline void write ( std::ostream & o, std::tupleT_PACK ... > const & t ) {
        o << ", " << std::get< i >( t );
        std__tuple< i + 1, n, T_PACK ... >::write( ot );
    }
};

template < typename ... T_PACK >
struct std__tuple< 0, 0, T_PACK ... > {
    static inline void write ( std::ostream &, std::tupleT_PACK ... > const & ) {
        //
    }
};

template < size_t n, typename ... T_PACK >
struct std__tuple< 0, n, T_PACK ... > {
    static inline void write ( std::ostream & o, std::tuple< T_PACK ... > const & t ) {
        o << ' ' << std::get< 0 >( t );
        std__tuple< 1, n, T_PACK ... >::write( o, t );
    }
};

template < size_t n, typename ... T_PACK >
struct std__tuple< n, n, T_PACK ... > {
    static inline void write ( std::ostream & o, std::tuple< T_PACK ... > const & ) {
        o << ' ';
    }
};

template < typename ... T_PACK >
inline std::ostream & operator << ( std::ostream & o, std::tupleT_PACK ... > const & t ) {
    o << '{';
    std__tuple< 0, sizeof...( T_PACK ), T_PACK ... >::write( ot );
    o << '}';
    return o;
}

int main () {

    std::array< std::string, 3 > a = {{ "a""b""c" }};
    std::cout << "a: " << a << '\n';
    
    std::dequeint > d = { 1, 2, 3};
    std::cout << "d: " << d << '\n';
    
    std::forward_list< std::string > fl = { "c""b""a" };
    std::cout << "fl: " << fl << '\n';

    std::list< std::string > l = { "a""b""c" };
    std::cout << "l: " << l << '\n';

    std::vectordouble > v = { 1.1, 2.2, 3.3 };
    std::cout << "v: " << v << '\n';
    
    std::vectorbool > b = { truefalsetrue };
    std::cout << "b: " << b << '\n';
    
    std::map< std::stringint > m = { { "a", 1 }, { "b", 2 }, { "c", 4 }, { "c", 3 } };
    std::cout << "m: " << m << '\n';

    std::multimap< std::stringint > mm = { { "a", 1 }, { "b", 2 }, { "c", 4 }, { "c", 3 } };
    std::cout << "mm: " << mm << '\n';

    std::setdouble > s = { 1.1, 2.2, 3.3, 3.3 };
    std::cout << "s: " << s << '\n';
    
    std::multisetdouble > ms = { 1.1, 2.2, 3.3, 3.3 };
    std::cout << "ms: " << ms << '\n';

    std::unordered_map< std::stringint > um = { { "a", 1 }, { "b", 2 }, { "c", 4 }, { "c", 3 } };
    std::cout << "um: " << um << '\n';

    std::unordered_multimap< std::stringint > umm = { { "a", 1 }, { "b", 2 }, { "c", 4 }, { "c", 3 } };
    std::cout << "umm: " << umm << '\n';

    std::unordered_setdouble > us = { 1.1, 2.2, 3.3, 3.3 };
    std::cout << "us: " << us << '\n';
    
    std::unordered_multisetdouble > ums = { 1.1, 2.2, 3.3, 3.3 };
    std::cout << "ums: " << ums << '\n';

    std::queueint > q;
    q.push( 1 );
    q.push( 2 );
    q.push( 3 );
    std::cout << "q: " << q << '\n';
    
    std::priority_queuedouble > pq;
    pq.push( 1.1 );
    pq.push( 2.2 );
    pq.push( 3.3 );
    std::cout << "pq: " << pq << '\n';

    std::stackint > stk;
    stk.push( 1 );
    stk.push( 2 );
    stk.push( 3 );
    std::cout << "stk: " << stk << '\n';

    auto t0 = std::make_tuple();
    std::cout << "t0: " << t0 << '\n';

    auto t1 = std::make_tuple( 1 );
    std::cout << "t1: " << t1 << '\n';

    auto t2 = std::make_tuple( 1, 2.2 );
    std::cout << "t2: " << t2 << '\n';

    auto t3 = std::make_tuple( 1, 2.2, "three" );
    std::cout << "t3: " << t3 << '\n';

    return 0;
}

And we executed the following command:

g++ -o main.exe main.cpp -std=c++0x -O3 -march=native -Wall -Wextra -Werror && ./main.exe

Then we would get the following output:

a: {{ a, b, c }}
d: { 1, 2, 3 }
fl: { c, b, a }
l: { a, b, c }
v: { 1.1, 2.2, 3.3 }
b: { 1, 0, 1 }
m: { { a, 1 }, { b, 2 }, { c, 4 } }
mm: { { a, 1 }, { b, 2 }, { c, 4 }, { c, 3 } }
s: { 1.1, 2.2, 3.3 }
ms: { 1.1, 2.2, 3.3, 3.3 }
um: { { c, 4 }, { a, 1 }, { b, 2 } }
umm: { { c, 4 }, { c, 3 }, { a, 1 }, { b, 2 } }
us: { 3.3, 2.2, 1.1 }
ums: { 3.3, 3.3, 2.2, 1.1 }
q: { 1, 2, 3 }
pq: { 3.3, 1.1, 2.2 }
stk: { 1, 2, 3 }
t0: {}
t1: { 1 }
t2: { 1, 2.2 }
t3: { 1, 2.2, three }

Over the next couple of posts, we will discuss why it works … and if there is a better way … even if it takes more type-ity type.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.