This page is a collection of useful C++ idioms. It draws on three libraries - the C++ standard library, the boost library and the Adobe source library. Each entry shows several different ways of achieving the same thing - many of them use standard algorithms to replace for loops. There's a discussion at the end of the page about whether it is worth using the standard algorithms.
The definitions required to make these examples work are at the end of the page.
If you find any mistakes or have any feedback please email me
for( std::vector< T >::iterator i( begin( v_T ) ); i != end( v_T ); ++i ) { f_T( *i ); }
std::for_each( begin( v_T ), end( v_T ), f_T );
std::for_each( begin( v_T ), end( v_T ), []( T const& i ) { f_T( i ); } );
adobe::for_each( v_T, f_T );
adobe::for_each( v_T, []( T const& i ) { f_T( i ); } );
for( T const& i : v_T ) { f_T( i ); }
for( std::vector< T >::iterator i( begin( v_T ) ); i != end( v_T ); ++i ) { f_T_int( *i, 10 ); }
std::for_each( begin( v_T ), end( v_T ), std::bind( f_T_int, _1, 10 ) );
std::for_each( begin( v_T ), end( v_T ), []( T const& i ) { f_T_int( i, 10 ); } );
adobe::for_each( v_T, std::bind( f_T_int, _1, 10 ) );
adobe::for_each( v_T, []( T const& i ) { f_T_int( i, 10 ); } );
for( T const& i : v_T ) { f_T_int( i, 10 ); }
for( std::vector< T >::iterator i( begin( v_T ) ); i != end( v_T ); ++i ) { i->mf_void(); }
std::for_each( begin( v_T ), end( v_T ), std::bind( &T::mf_void, _1 ) );
std::for_each( begin( v_T ), end( v_T ), []( T const& i ) { i.mf_void(); } );
adobe::for_each( v_T, std::bind( &T::mf_void, _1 ) );
adobe::for_each( v_T, []( T const& i ) { i.mf_void(); } );
for( T const& i : v_T ) { i.mf_void(); }
int j( 7 ); for( std::vector< T >::iterator i( begin( v_T ) ); i != end( v_T ); ++i ) { i->mf_int( j ); }
std::for_each( begin( v_T ), end( v_T ), std::bind( &T::mf_int, _1, j ) );
std::for_each( begin( v_T ), end( v_T ), std::bind2nd( std::mem_fun_ref( &T::mf_int ), j ) );
std::for_each( begin( v_T ), end( v_T ), [ j ]( T const& i ) { i.mf_int( j ); } );
adobe::for_each( v_T, std::bind( &T::mf_int, _1, j ) );
adobe::for_each( v_T, std::bind2nd( std::mem_fun_ref( &T::mf_int ), j ) );
adobe::for_each( v_T, [ j ]( T const& i ) { i.mf_int( j ); } );
for( T const& i : v_T ) { i.mf_int( j ); }
for( std::vector< T >::iterator i( begin( v_T ) ); i != end( v_T ); ++i ) { i->mf_T( *i ); }
std::for_each( begin( v_T ), end( v_T ), std::bind( &T::mf_T, _1, _1 ) );
std::for_each( begin( v_T ), end( v_T ), []( T const& t ) { t.mf_T( t ); } );
adobe::for_each( v_T, std::bind( &T::mf_T, _1, _1 ) );
adobe::for_each( v_T, []( T const& t ) { t.mf_T( t ); } );
for( T const& i : v_T ) { i.mf_T( i ); }
std::vector< T >::iterator i; for( i = begin( v_T ); i != end( v_T ); ++i ) { if( i->value() == 3 ) break; }
i = std::find_if( begin( v_T ), end( v_T ), std::bind( std::equal_to< int >(), std::bind( &T::value, _1 ), 3 ) );
i = std::find_if( begin( v_T ), end( v_T ), []( T const& t ) { return t.value() == 3; } );
i = adobe::find_if( v_T, std::bind( std::equal_to< int >(), std::bind( &T::value, _1 ), 3 ) );
i = adobe::find_if( v_T, []( T const& t ) { return t.value() == 3; } );
int n = 0; for( std::vector< int >::iterator i( begin( v_int ) ); i != end( v_int ); ++i ) { n += *i; }
n = std::accumulate( begin( v_int ), end( v_int ), 0 );
n = adobe::accumulate( v_int, 0 );
n = 0; for( int i : v_int ) { n += i; }
int n = 1; for( std::vector< int >::iterator i( begin( v_int ) ); i != end( v_int ); ++i ) { n *= *i; }
n = std::accumulate( begin( v_int ), end( v_int ), 1, std::multiplies< int >() );
n = std::accumulate( begin( v_int ), end( v_int ), 1, []( int i, int elem ) { return i * elem; } );
n = adobe::accumulate( v_int, 1, std::multiplies< int >() );
n = adobe::accumulate( v_int, 1, []( int i, int elem ) { return i * elem; } );
n = 1; for( int i : v_int ) { n *= i; }
int n; n = 0; for( std::vector< T >::iterator i( begin( v_T ) ); i != end( v_T ); ++i ) { n += i->value(); }
n = std::accumulate( begin( v_T ), end( v_T ), 0, std::bind( std::plus< int >(), _1, std::bind( &T::value, _2 ) ) );
n = std::accumulate( begin( v_T ), end( v_T ), 0, []( int i, T const& t ) { return i + t.value(); } );
n = adobe::accumulate( v_T, 0, std::bind( std::plus< int >(), _1, std::bind( &T::value, _2 ) ) );
n = adobe::accumulate( v_T, 0, []( int i, T const& t ) { return i + t.value(); } );
n = 0; for( T const& t : v_int ) { n += t.value(); }
std::vector< std::string > vs; for( std::map< std::string, T >::iterator i( begin( m_str_T ) ); i != end( m_str_T ); ++i ) { vs.push_back( i->first ); }
std::transform( begin( m_str_T ), end( m_str_T ), std::back_inserter( vs ), std::bind( &std::map< std::string, T >::value_type::first, _1 ) );
std::transform( begin( m_str_T ), end( m_str_T ), std::back_inserter( vs ), select1st< std::map< std::string, T >::value_type >() );
std::transform( begin( m_str_T ), end( m_str_T ), std::back_inserter( vs ), []( std::map< std::string, T >::value_type const& v ) { return v.first; } );
adobe::transform( m_str_T, std::back_inserter( vs ), std::bind( &std::map< std::string, T >::value_type::first, _1 ) );
adobe::transform( m_str_T, std::back_inserter( vs ), select1st< std::map< std::string, T >::value_type >() );
adobe::transform( m_str_T, std::back_inserter( vs ), []( std::map< std::string, T >::value_type const& v ) { return v.first; } );
for( std::map< std::string, T >::value_type const& v : m_str_T ) { vs.push_back( v.first ); }
for( std::map< std::string, T >::iterator i( begin( m_str_T ) ); i != end( m_str_T ); ++i ) { v_T.push_back( i->second ); }
std::transform( begin( m_str_T ), end( m_str_T ), std::back_inserter( v_T ), std::bind( &std::map< std::string, T >::value_type::second, _1 ) );
std::transform( begin( m_str_T ), end( m_str_T ), std::back_inserter( v_T ), select2nd< std::map< std::string, T >::value_type >() );
std::copy( boost::make_transform_iterator( begin( m_str_T ), select2nd< std::map< std::string, T >::value_type >() ), boost::make_transform_iterator( end( m_str_T ), select2nd< std::map< std::string, T >::value_type >() ), std::back_inserter( v_T ) );
adobe::transform( m_str_T, std::back_inserter( v_T ), select2nd< std::map< std::string, T >::value_type >() );
adobe::transform( m_str_T, std::back_inserter( v_T ), std::bind( &std::map< std::string, T >::value_type::second, _1 ) );
adobe::transform( m_str_T, std::back_inserter( v_T ), []( std::map< std::string, T >::value_type const& v ){ return v.second; } );
for( std::map< std::string, T >::value_type const& v : m_str_T ) { v_T.push_back( v.second ); }
std::ostringstream ostr; for( std::vector< T >::iterator i( begin( v_T ) ); i != end( v_T ); ++i ) { ostr << *i << "\n"; }
std::copy( begin( v_T ), end( v_T ), std::ostream_iterator< T >( ostr, "\n" ) );
adobe::copy( v_T, std::ostream_iterator< T >( ostr, "\n" ) );
for( T const& t : v_T ) { ostr << t << "\n"; }
std::string fileAsString; std::ifstream istr( fileName.c_str(), std::ios::binary ); do { char ch( istr.get() ); if( istr ) fileAsString.push_back( ch ); } while( istr );
fileAsString = std::string( std::istreambuf_iterator< char >( istr ), std::istreambuf_iterator< char >() );
std::copy( std::istreambuf_iterator< char >( istr ), std::istreambuf_iterator< char >(), std::back_inserter( fileAsString ) );
for( std::vector< T >::iterator i( begin( v_T ) ); i != end( v_T ); ++i ) { v_int.push_back( f_int_T( *i ) ); }
std::transform( begin( v_T ), end( v_T ), std::back_inserter( v_int ), f_int_T );
std::transform( begin( v_T ), end( v_T ), std::back_inserter( v_int ), []( T const& t ) { return f_int_T( t ); } );
adobe::transform( v_T, std::back_inserter( v_int ), f_int_T );
adobe::transform( v_T, std::back_inserter( v_int ), []( T const& t ) { return f_int_T( t ); } );
for( T const& t : v_T ) { v_int.push_back( f_int_T( t ) ); }
for( std::vector< T >::iterator i( begin( v_T ) ); i != end( v_T ); ++i ) { v_str.push_back( f_str_int( f_int_T( *i ) ) ); }
std::transform( begin( v_T ), end( v_T ), std::back_inserter( v_str ), std::bind( adobe::compose( std::ptr_fun( f_str_int ), std::ptr_fun( f_int_T ) ), _1 ) );
std::transform( begin( v_T ), end( v_T ), std::back_inserter( v_str ), std::bind( f_str_int, std::bind( f_int_T, _1 ) ) );
std::transform( begin( v_T ), end( v_T ), std::back_inserter( v_str ), []( T const& t ) { return f_str_int( f_int_T( t ) ); } );
adobe::transform( v_T, std::back_inserter( v_str ), std::bind( adobe::compose( std::ptr_fun( f_str_int ), std::ptr_fun( f_int_T ) ), _1 ) );
adobe::transform( v_T, std::back_inserter( v_str ), std::bind( f_str_int, std::bind( f_int_T, _1 ) ) );
adobe::transform( v_T, std::back_inserter( v_str ), []( T const& t ) { return f_str_int( f_int_T( t ) ); } );
for( T const& t : v_T ) { v_str.push_back( f_str_int( f_int_T( t ) ) ); }
for( std::map< std::string, T >::iterator i( begin( m_str_T ) ); i != end( m_str_T ); ++i ) { f_T( i->second ); }
std::for_each( begin( m_str_T ), end( m_str_T ), std::bind( f_T, std::bind( select2nd< std::map< std::string, T >::value_type >(), _1 ) ) );
std::for_each( begin( m_str_T ), end( m_str_T ), []( std::map< std::string, T >::value_type const& v ) { f_T( v.second ); } );
adobe::for_each( m_str_T, std::bind( f_T, std::bind( select2nd< std::map< std::string, T >::value_type >(), _1 ) ) );
adobe::for_each( m_str_T, []( std::map< std::string, T >::value_type const& v ) { f_T( v.second ); } );
for( std::map< std::string, T >::value_type const& v : m_str_T ) { f_T( v.second ); }
(Only works when calling a const function)
for( std::map< std::string, T >::iterator i( begin( m_str_T ) ); i != end( m_str_T ); ++i ) { i->second.mf_int( 7 ); }
std::for_each( begin( m_str_T ), end( m_str_T ), std::bind( &T::mf_int, std::bind( &std::map< std::string, T >::value_type::second, _1 ), 7 ) );
std::for_each( begin( m_str_T ), end( m_str_T ), []( std::map< std::string, T >::value_type const& v ) { v.second.mf_int( 7 ); } );
adobe::for_each( m_str_T, std::bind( &T::mf_int, std::bind( select2nd< std::map< std::string, T >::value_type >(), _1 ), 7 ) );
adobe::for_each( m_str_T, []( std::map< std::string, T >::value_type const& v ) { v.second.mf_int( 7 ); } );
for( std::map< std::string, T >::value_type const& v : m_str_T ) { v.second.mf_int( 7 ); }
using
statements would make most of this code shorter. I have chosen not to use these to make it explicit where the various functions and adapters are coming from.bind1st
or bind2nd
in any of these examples. The hoops they make you jump through are much too convoluted for my taste. boost::bind
provides a much more understandable way of achieving the same thing.Is it worth using the standard algorithms? Here are a few factors to take into consideration.
All else being equal, shorter is better than longer. Unfortunately all else is rarely equal.
There are arguments in both directions here. The standard algorithms allow the programmer to
separate the looping from what goes on in the loop. When I see a for loop I have to check the
condition and the increment to see exactly what it's looping over. I have to look at the body
of the loop to see if there are any other changes to the looping variable in there. When I
see std::for_each
I know that the loop will access every element in the given
range. When I see adobe::for_each
I know that the loop will acccess every element
in the container. When I see std::transform
I know that the loop produces the same
number of values that the input range has, and that there is a one to one correspondance between
input and output values.
On the other hand, when I see code like this:
std::transform( v_T.begin(), v_T.end(), std::back_inserter( v_str ), boost::bind( f_str_int, boost::bind( f_int_T, _1 ) ) );
I have to work pretty hard to work out what's going on. My usual limit is one boost::bind statement in an algorithm expression. Any more than that and I write a separate function or function object. For more discussion see Scott Meyers' Effective STL Item 47: Avoid producing write-only code.
Code doesn't just have to work, it has to be maintainable by you and by other people on your team.
Writing a separate function or function object is a reasonable technique, but the code is now split between the original site and the new function. In the applications I have written there isn't a lot of reuse of functions used in algorithms. I don't know how generally this applies.
On the one hand the code is now split into two places, on the other hand, if the function is well named it might make the behaviour of the code easier to understand. I think that splitting the looping from what's happening at each iteration of the loop is beneficial - using the standard algorithms pushes you in that direction.
If efficiency is of concern there's no substitute for doing your own tests under your own circumstances. Chris Cox has an interesting blog examining C++ performance and the abstraction penalty of using standard algorithms.
class T { public: T( int v ); T( T const& ); T& operator =( T const& ); int value() const; void mf_void() const; void mf_int( int ) const; void mf_T( T const& ) const; private: int v_; }; bool operator ==( T const&, T const& ); std::ostream& operator <<( std::ostream&, T const& ); void f_T( T const& ); void f_T_int( T const&, int ); std::vector< int > v_int; std::vector< T > v_T; std::vector< std::string > v_str; std::map< std::string, T > m_str_T; templatestruct select1st : public std::unary_function { const typename Pair::first_type& operator()(const Pair& x) const { return x.first; } }; template struct select2nd : public std::unary_function { const typename Pair::second_type& operator()(const Pair& x) const { return x.second; } typename Pair::second_type& operator()(Pair& x) { return x.second; } };
This page last modified 11 February 2014