Author: | Thorsten Ottosen |
---|---|
Contact: | nesotto@cs.aau.dk |
Organizations: | Dezide Aps and Aalborg University |
Date: | 2005-04-27 |
Number: | WG21/N1796 and J16/05-0056 |
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:
Performance. A third 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.
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 should also work with a wide variety of ranges. For example:
// builtin arrays const int[] integers = { 1, 2, 3, 4, 5, 6 }; for( int i : integers ) 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.
This proposal suggest that 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 for( auto __begin = begin(vec), __end = end(vec); __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 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() const { return data_; } char* End() const { return data_ + size; } }; // for-loop requirements std::if_< std::is_const<MyContainer>, const char*, char*>::type begin( MyContainer&& c ) { return c.Begin(); } std::if_< std::is_const<MyContainer>, const char*, char*>::type end( MyContainer&& c ) { return c.End(); }
Notice that even though the original container was not const-correct, the new range interface is. Alternatively the user may just write an adapter
class my_range_adapter { MyContainer& c; public: my_range_adapter( MyContainer& r ) : c(r) { } char* begin() { return c.Begin(); } const char* begin() const { return c.Begin(); } char* end() { return c.End(); } const char* end() const { return c.End(); } }; ... // usage MyContainer m = ...; for( char c : my_range_adapter(m) ) ...;
Basically we here exploit that the default version of .begin()/.end() are such that they work with standard containers.
As a third alternative the user may apply one of the utilities that comes with the standard library:
for( char c : std::make_iterator_range(m.Begin(),m.End()) ) ...
The author would like to thank Lawrence Crowl, Bjarne Stroustrup, Doug Gregor and Daveed Vandevoorde.