Author: | Thorsten Ottosen |
---|---|
Contact: | nesotto@cs.aau.dk |
organizations: | Dezide Aps and Aalborg University |
Date: | 2005-08-24 |
Number: | WG21/N1868 and J16/05-0128 |
Working Group: | Evolution |
Abstract
Just about any modern language has some form of "for each" built into it. This paper introduces a new "for each" like for-loop and describes the necessary core-language changes.
Being able to iterate over a range of values is common operation that is unnecessarily difficult and verbose in current C++. This makes the language harder to use for novice and experienced programmers alike.
The benefit of a new for loop is two-fold:
Accessibility. Just about any modern language has added a "for each" construct and C++ should do the same to make the language more accessible.
Performance. A second benefit is that a more consistent performance of the loop is guaranteed; for example, it is quite common to write
for( container::iterator i = c.begin(); i != c.end(); ++i ) ...
which leads to repeated calls to c.end(). Moreover, the new for loop might be easier parallelized.
This proposal is based on the discussion following Tom Plum's presentation at the 2004 meeting in Sydney.
There is a continued interest in a better for loop for C++ (no pun intended). For example, Eric Niebler's BOOST_FOREACH macro has recently been accepted into Boost (not yet part of the distribution) and Trolltech has provided a foreach macro for QT 4.
The new for-loop is based on half-open iterator ranges of the form [begin,end). An example of its usage could be
vector<int> vec = ...; for( int i : vec ) std::cout << i;
Remark: the above syntax received consensus in Sydney.
One can also access the values of the range as references
vector<int> vec = ...; for( int& i : vec ); i = 42;
The construct works with a wide variety of ranges. For example:
// builtin arrays int an_array[] = {...}; for( char c : an_array ) std::cout << c; // pairs of iterators typedef vector<int>::iterator iter; std::pair<iter,iter> p = ...; for( int i : p ) std::cout << i;
Remark: this proposal deals only with the core-language mechanism that makes the loop work with a range; a separate proposal will consider the necessary library extensions because the library components are of great value in many other contexts.
The proposal is thus dependent on the following other C++0x proposals:
The new for-loop is based on the compiler calling standard library functions. A loop like
for( int i : vec ) std::cout << i;
is translated into
using std::begin; // enable ADL using std::end; // ditto auto&& __rng( vec ); for( auto __begin = begin(__rng), __end = end(__rng); __begin != __end; ++__begin ) { int i = *__begin; std::cout << i; }
Notice that the end iterator is only calculated once.
The user is required to include the standard header <iterator> in which the default version of begin()/end() is defined:
namespace std { template< class T > auto begin( T&& t ) -> decltype( t.begin() ) { return t.begin(); } template< class T > auto end( T&& t ) -> decltype( t.end() ) { return t.end(); } }
The entire library support needed for the new loop may be found in n1871 (Part 1).
The protocol for making a non-conforming UDT work with the new for loop is quite simple:
// UDT class MyContainer { char* data_; size_t size_; public: char* Begin() { return data_; } const char* Begin() const { return data_; } char* End() { return data_ + size; } const char* End() const { return data_ + size; } }; // for-loop requirements char* begin( MyContainer& c ) { return c.Begin(); } const char* begin( const MyContainer& c ) { return c.End(); } char* begin( MyContainer& c ) { return c.Begin(); } const char* begin( const MyContainer& c ) { return c.End(); }
We then rely on ADL for finding the right version of begin() and end().
Alternatively the user may apply one of the utilities that comes with the library:
MyContainer m = ...; for( char c : std::make_range(m.Begin(),m.End()) ) ...
One could also imagine a different design which did not rely on inclusion of the <iterator> header.
The benefits would be
The downsides would be
Including <iterator should not be seen as a big problem, though. It is impossible not to include it implicitly when using one of the standard containers.
The author would like to thank Lawrence Crowl, Bjarne Stroustrup, Doug Gregor, Daveed Vandevoorde and Peter Dimov.