Proposal for new for-loop (revision 1)

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.

1 Motivation

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.

2 Examples

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:

3 The proposal

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).

4 Making a custom type work with the for loop

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()) )
 ... 

5 Alternative design

One could also imagine a different design which did not rely on inclusion of the <iterator> header.

The benefits would be

  • it is simpler
  • slightly easier support for UDTs

The downsides would be

  • support for eg. pair<iterator,iterator> would require an adapter

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.

6 Acknowledgements

The author would like to thank Lawrence Crowl, Bjarne Stroustrup, Doug Gregor, Daveed Vandevoorde and Peter Dimov.

AltStyle によって変換されたページ (->オリジナル) /