Writing with Iterators in C++ 2011

From my “Printing Standard Library Sequence and Associative Containers in C++ 2011” post, let’s discuss the very first template:

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 << '}';
}

In the days of C++ 1998/2003, the Standard Library containers adopted functionality and syntax from arrays and … Standard Library iterators adopted functionality and syntax from pointers. This adoption allowed us to create the following similar code for both arrays and Standard Library containers (like std::vector):

#include <iostream>
#include <vector>
 
int main () {
 
    int a[] = { 1, 2, 3 };
 
    std::cout << "a: ";
    std::cout << '{';
    int const * a_i = &a[ 0 ];
    int const * a_end = &a[ sizeof( a ) / sizeofint ) ];
    if ( a_i != a_end ) {
        std::cout << ' ' << *a_i;
        for ( ++a_i; a_i != a_end; ++a_i )
            std::cout << ", " << *a_i;
        std::cout << ' ';
    }
    std::cout << '}';
    std::cout << '\n';
 
    std::vectorint > v;
    v.push_back( 1 );
    v.push_back( 2 );
    v.push_back( 3 );
 
    std::cout << "v: ";
    std::cout << '{';
    std::vectorint >::const_iterator v_i = v.begin();
    std::vectorint >::const_iterator v_end = v.end();
    if ( v_i != v_end ) {
        std::cout << ' ' << *v_i;
        for ( ++v_i; v_i != v_end; ++v_i )
            std::cout << ", " << *v_i;
        std::cout << ' ';
    }
    std::cout << '}';
    std::cout << '\n';
 
    return 0;
}

Unfortunately, the code didn’t have the same implementation even though it had the same structure and used the same ideas. Arrays didn’t have a begin() or end() method … although &a[ 0 ] and &a[ sizeof( a ) / sizeof( type of a ) ] are the same ideas. Arrays also didn’t have a const_iterator typedef … although a pointer to the const version of the type of a is the same idea. There were many interesting ways around this disparity … all involving a lot of type-ity type. In C++ 2011, std::begin(), std::end(), and auto allow this structure and these ideas to be realized in the same implementation. Moreover, the ideas behind array initialization have been applied to most Standard Library containers and implemented using the same syntax (using std::initializer_list) … so now we can have the following main.cpp:

#include <iostream>
#include <vector>
 
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 << '}';
}
 
int main () {
 
    int a[] = { 1, 2, 3 };
 
    std::cout << "a: ";
    write( std::cout, a );
    std::cout << '\n';
 
    std::vectorint > v = { 1, 2, 3 };
 
    std::cout << "v: ";
    write( std::cout, v );
    std::cout << '\n';
 
    return 0;
}

Once we execute the following command line:

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

We get the following output:

a: { 1, 2, 3 }
v: { 1, 2, 3 }

This “write” template prints all the values within the Standard Library containers in an “initializer list” syntax.

In the next post, we’ll discuss how the following traditional operator << function gets a variadic-template makeover and uses “write” to do its dirty work:

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

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.