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 & o, C 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 ) / sizeof( int ) ]; 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::vector< int > v; v.push_back( 1 ); v.push_back( 2 ); v.push_back( 3 ); std::cout << "v: "; std::cout << '{'; std::vector< int >::const_iterator v_i = v.begin(); std::vector< int >::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 & o, C 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::vector< int > 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 < typename, typename, typename ... > class C, typename T1, typename T2, typename ... T_PACK > inline std::ostream & operator << ( std::ostream & o, C< T1, T2, T_PACK ... > const & c ) { write( o, c ); return o; }
- Printing Standard Library Sequence and Associative Containers in C++ 2011
- Matching Standard Library Containers Using Variadic Templates in C++ 2011