Author: | Thorsten Ottosen |
---|---|
Contact: | tottosen@dezide.com |
organizations: | Dezide Aps |
Date: | 2006-09-08 |
Number: | WG21/N2068 J16/06-0138 (revision of WG21/N1871 and J16/05-0131) |
Working Group: | Evolution |
Abstract
This paper proposes the addition of ranges to the C++ standard library to reduce the syntactic burden on programmers. The focus is on the minimal core features that enables enhanced algorithm interfaces.
A range is simply an encapsulation of two iterators. The term range is due to paragraph 24.1/7 from the C++ standard:
Most of the library's algorithmic templates that operate on data structures have interfaces that use ranges. A range is a pair of iterators that designate the beginning and end of the computation. A range [i, i) is an empty range; in general, a range [i, j) refers to the elements in the data structure starting with the one pointed to by i and up to but not including the one pointed to by j. Range [i, j) is valid if and only if j is reachable from i. The result of the application of functions in the library to invalid ranges is undefined.
Even though standard algorithms are specified in terms of iterator ranges, they are still somewhat clumsy to use. By combining the two iterators into one object, we can achieve great benefits. This proposal will give standard C++
- a drastic reduction in syntactic overhead,
- a new concise idiomatic use of algorithms and loops,
- safer use of built-in arrays.
For more discussion, please refer to n1871.
The new string algorithms in n2059 builds on the core range primitives from this paper and this library component can thus be seen as a prerequisite for the string algorithms.
Each numbered section below describes a new section for the standard or modifications to an existing section. Comments are written in bold and are not part of the wording
Add the following to the end of the synopsis:
template< class Range > auto range_begin( Range&& r ) -> decltype( r.begin() ); template< class Iterator > Iterator range_begin( const pair<Iterator,Iterator>& p ) ; template< class T, size_t N > T* range_begin( T (&array)[N] ) template< class Range > auto begin( Range&& r ) -> decltype( range_begin(r) ); template< class Range > auto range_end( Range&& r ) -> decltype( r.end() ); template< class Iterator > Iterator range_end( const pair<Iterator,Iterator>& p ); template< class T, size_t N > T* range_end( T (&array)[N] ); template< class Range > auto end( Range&& r ) -> decltype( range_end(r) ); template< class Range > struct range_iterator; template< class BidirectionalRange > struct range_reverse_iterator; template< class Range > struct range_value; template< class Range > struct range_reference; template< class Range > struct range_pointer; template< class Range > struct range_difference; template< class Range > struct range_category; template< class Range > bool empty( const Range& r ); template< class RandomAccessRange > typename range_difference<RandomAccessRange>::type size( const RandomAccessRange& r ); template< class BidirectionalRange > typename range_reverse_iterator<BidirectionalRange>::type rbegin( BidirectionalRange&& r ); template< class BidirectionalRange > typename range_reverse_iterator<BidirectionalRange>::type rend( BidirectionalRange&& r ); template< class Range > typename range_iterator<const Range>::type const_begin( const Range& r ); template< class Range > typename range_iterator<const Range>::type const_end( const Range& r ); template< class BidirectionalRange > typename range_reverse_iterator<const BidirectionalRange>::type const_rbegin( const BidirectionalRange& r ); template< class BidirectionalRange > typename range_reverse_iterator<const BidirectionalRange>::type const_rend( const BidirectionalRange& r ); template< class Iterator > class range; template< class Range > class sub_range : public range< typename range_iterator<Range>::type >; // stream output template< class Iterator, class T, class Traits > std::basic_ostream<T,Traits>& operator<<( std::basic_ostream<T,Traits>& Os, const range<Iterator>& r ); // comparison template< class Iterator, class Iterator2 > bool operator==( const range<Iterator>& l, const range<Iterator2>& r ); template< class Iterator, class Range > bool operator==( const range<Iterator>& l, const Range& r ); template< class Iterator, class Range > bool operator==( const Range& l, const range<Iterator>& r ); template< class Iterator, class Iterator2 > bool operator!=( const range<Iterator>& l, const range<Iterator2>& r ); template< class Iterator, class Range > bool operator!=( const range<Iterator>& l, const Range& r ); template< class Iterator, class Range > bool operator!=( const Range& l, const range<Iterator>& r ); template< class Iterator, class Iterator2 > bool operator<( const range<Iterator>& l, const range<Iterator2>& r ); template< class Iterator, class Range > bool operator<( const range<Iterator>& l, const Range& r ); template< class Iterator, class Range > bool operator<( const Range& l, const range<Iterator>& r ); template< class Range, class Range2 > bool operator==( const sub_range<Range>& l, const sub_range<Range2>& r ); template< class Range, class Range2 > bool operator!=( const sub_range<Range>& l, const sub_range<Range2>& r ) template< class Range, class Range2 > bool operator<( const sub_range<Range>& l, const sub_range<Range2>& r ); // external construction template< class Iterator > range<Iterator> make_range( const Iterator& begin, const Iterator& end ); template< class Range > range<typename range_iterator<Range>::type> make_range( Range&& r ); template< class Range > range<typename range_iterator<Range>::type> make_range( Range&& r, typename range_difference<Range>::type advance_begin, typename range_difference<Range>::type advance_end ); template< class CopyableRange, class Range > CopyableRange copy_range( const Range& r ); template< class ForwardRange > typename range_difference<ForwardRange>::type distance( const ForwardRange& r );
Add the following new section:
To support ranges and range-based algorithms, the library provides free-standing functions that enable the range abstraction as well as several utilities.
A type T conforms to a range concept if the free-standing functions range_begin(T&) and range_begin(T&) have been defined in the namespace of T or if T has member functions named begin() and end(). Furthermore, built-in arrays conforms to the range concept. All these functions must return iterators in amortized constant time, and the type of these iterators may vary with the constness of the range object. If the iterators of a range are mutable, we say the range is mutable, and if the iterators of a range are constant, we say the range is constant.
Each possible category of the associated iterator type of a range leads to a range category; we therefore have the concept of input ranges, forward ranges, bidirectional ranges and random access ranges. There is no notion of an output range. A range is singular if any of its associated iterators are singular.
If a range T owns its associated values (like containers do) and has a deep-copy constructor that takes two iterator parameters, T is called a copyable range.
The functions range_begin() and range_end() are intended to be overloaded for a user-defined type T in the namespace of T.
The functions begin() and end() are intended to be called qualified with the std namespace, and they are always called qualified within the standard library. From these two functions the call to range_begin() and range_end(), respectively, is without namespace qualification to enable argument dependent lookup.
template< class Range > auto range_begin( Range&& r ) -> decltype( r.begin() );
template< class Iterator > Iterator range_begin( const pair<Iterator,Iterator>& p );
template< class T, size_t N > T* range_begin( T (&array)[N] )
template< class Range > auto begin( Range&& r ) -> decltype( range_begin(r) );
template< class Range > auto range_end( Range&& r ) -> decltype( r.end() );
template< class Iterator > Iterator range_end( const pair<Iterator,Iterator>& p );
template< class T, size_t N > T* range_end( T (&array)[N] );
template< class Range > auto end( Range&& r ) -> decltype( range_end(r) );
template< class RandomAccessRange > typename range_difference<RandomAccessRange>::type size( const RandomAccessRange& r );
template< class BidirectionalRange > typename range_reverse_iterator<BidirectionalRange>::type rbegin( BidirectionalRange&& r );
template< class BidirectionalRange > typename range_reverse_iterator<BidirectionalRange>::type rend( BidirectionalRange&& r );
template< class Range > typename range_iterator<const Range>::type const_begin( const Range& r );
template< class Range > typename range_iterator<const Range>::type const_end( const Range& r );
template< class BidirectionalRange > typename range_reverse_iterator<const BidirectionalRange>::type const_rbegin( const BidirectionalRange& r );
template< class BidirectionalRange > typename range_reverse_iterator<const BidirectionalRange>::type const_rend( const BidirectionalRange& r );
For convenience the type and properties of an associated iterator type of a range can be queried by range traits. Each trait is a TransformationTrait and so define a nested type called type.
template< class Range > struct range_iterator;
template< class BidirectionalRange > struct range_reverse_iterator;
template< class Range > struct range_value;
template< class Range > struct range_reference;
template< class Range > struct range_pointer;
template< class Range > struct range_difference;
template< class Range > struct range_category;
For convenience the library provides two iterator-view helper classes. These two classes make comparison between view-objects and container-objects easy and provide a safer and more convenient encapsulation of iterator-views.
namespace std { template< class Iterator > class range { public: // types typedef typename iterator_traits<Iterator>::value_type value_type; typedef typename iterator_traits<Iterator>::reference reference; typedef typename iterator_traits<Iterator>::difference_type difference_type; typedef Iterator iterator; typedef Iterator const_iterator; public: // construction, assignment range(); template< class Iterator2 > range( const Iterator2& begin, const Iterator2& end ); template< class Range > range( Range&& r ); template< class Range > range& operator=( Range&& r ); public: // forward range functions iterator begin() const; iterator end() const; difference_type size() const; bool empty() const; public: // convenience operator unspecified_bool_type() const; bool equal( const range& ) const; reference front() const; reference back() const; reference operator[]( difference_type at ) const; range& advance_begin( difference_type n ); range& advance_end( difference_type n ); private: Iterator first; // exposition only Iterator last; // exposition only }; template< class Range > class sub_range : public range< typename range_iterator<Range>::type > { public: typedef range< typename range_iterator<Range>::type > base_type typedef typename range_iterator<Range>::type iterator; typedef typename range_iterator<const Range>::type const_iterator; typedef typename iterator_traits<const_iterator>::reference const_reference; using base_type::value_type; using base_type::reference; using base_type::difference_type; public: // construction, assignment sub_range(); template< class Iterator > sub_range( const Iterator& begin, const Iterator& end ); template< class Range2 > sub_range( Range2&& r ); template< class Range2 > sub_range& operator=( Range2&& r ); public: // forward range functions iterator begin(); const_iterator begin() const; iterator end(); const_iterator end() const; public: // convenience reference front(); const_reference front() const; reference back(); const_reference back() const; reference operator[]( difference_type at ); const_reference operator[]( difference_type at ) const; sub_range& advance_begin( difference_type n ); sub_range& advance_end( difference_type n ); }; }
[Example: An efficient way to find and manipulate a substring may be done with sub_range:
string s = ...; sub_range<string> sub = find( s, ... ); if( sub == string("foo") ) { sub[0] = 'b'; assert( sub == string("boo") ); }
-- end example]
range();
template< class Iterator2 > range( const Iterator2& begin, const Iterator2& end );
template< class Range > range( Range&& r );
template< class Range > range& operator=( Range&& r );
iterator begin() const;
iterator end() const;
difference_type size() const;
bool empty() const;
operator unspecified_bool_type() const;
bool equal( const range& ) const;
reference front() const;
reference back() const;
reference operator[]( difference_type at ) const;
range& advance_begin( difference_type n );
range& advance_end( difference_type n );
sub_range();
template< class Iterator > sub_range( const Iterator& begin, const Iterator& end );
template< class Range2 > sub_range( Range2&& r );
template< class Range2 > sub_range& operator=( Range2&& r );
iterator begin();
const_iterator begin() const;
iterator end();
const_iterator end() const;
reference front();
const_reference front() const;
reference back();
const_reference back() const;
reference operator[]( difference_type at );
const_reference operator[]( difference_type at ) const;
sub_range& advance_begin( difference_type n );
sub_range& advance_end( difference_type n );
template< class Iterator, class T, class Traits > std::basic_ostream<T,Traits>& operator<<( std::basic_ostream<T,Traits>& Os, const range<Iterator>& r );
template< class Iterator, class Iterator2 > bool operator==( const range<Iterator>& l, const range<Iterator2>& r );
template< class Iterator, class Range > bool operator==( const range<Iterator>& l, const Range& r );
template< class Iterator, class Range > bool operator==( const Range& l, const range<Iterator>& r );
template< class Iterator, class Iterator2 > bool operator!=( const range<Iterator>& l, const range<Iterator2>& r );
template< class Iterator, class Range > bool operator!=( const range<Iterator>& l, const Range& r );
template< class Iterator, class Range > bool operator!=( const Range& l, const range<Iterator>& r );
template< class Iterator, class Iterator2 > bool operator<( const range<Iterator>& l, const range<Iterator2>& r );
template< class Iterator, class Range > bool operator<( const range<Iterator>& l, const Range& r );
template< class Iterator, class Range > bool operator<( const Range& l, const range<Iterator>& r );
template< class Range, class Range2 > bool operator==( const sub_range<Range>& l, const sub_range<Range2>& r );
template< class Range, class Range2 > bool operator!=( const sub_range<Range>& l, const sub_range<Range2>& r )
template< class Range, class Range2 > bool operator<( const sub_range<Range>& l, const sub_range<Range2>& r );
template< class Iterator > range<Iterator> make_range( const Iterator& begin, const Iterator& end );
template< class Range > range<typename range_iterator<Range>::type> make_range( Range&& r );
template< class Range > range<typename range_iterator<Range>::type> make_range( Range&& r, typename range_difference<Range>::type N, typename range_difference<Range>::type M );
template< class CopyableRange, class Range > CopyableRange copy_range( const Range& r );
template< class ForwardRange > typename range_difference<ForwardRange>::type distance( const ForwardRange& r );
Thanks to Pavol Droba for our many discussions. Thanks to Howard Hinnant, Dietmar Kuehl and Walter Brown for feedback on the earlier version of this paper. Thanks to the Boost community for their feedback on Boost.Range over the years.