Reputation: 18375
As far as I can tell, the idiom for iterating over STL collections looks something like this:
int a[] = { 1,2,3 };
std::vector<int> v(a, a+3);
std::for_each(a.begin(), a.end(), some_function);
Specifying the first and last iterators is useful if I want to only work on a certain range of the collection, or do something more creative, but most of the time, I suspect we're actually wanting to work with the whole collection. So, I'm wondering why people bother specifying the iterators in that situation (since they'll always be the same), and don't just use a convenience function along these lines:
namespace easy
{
template <class T, class F>
F for_each(T& collection, F function)
{
std::for_each(collection.begin(), collection.end(), function);
return function;
}
}
(Of course, it's possible that this is the idiomatic way of doing things and I never noticed! I'm new to C++, though.)
Upvotes: 2
Views: 260
Reputation: 40613
Specifying the begin and end iterators instead of just the collection is indeed tedious and error prone (for example your example code erroneously tried to call .begin()
and .end()
on a
instead of v
). That is why Boost Range was invented. With it you are able to write code such as:
int a[] = { 1,2,3 };
boost::for_each(a, some_function);
It also introduces the notion of range adaptors, which can be composed with algorithms to multiply their usefulness and generality.
[Speculation] The reason that STL uses iterators instead of ranges is that it was conceived from the viewpoint of constructing algorithms and then finding the minimum requirements for those algorithms and expressing them in terms of those requirements. Algorithms require iterators in order do their job, even though the natural thing that algorithms are used on are actually ranges of values. As was mentioned in another answer the STL was under severe time pressure, and so these issues were never worked out. Luckily us modern folks have Boost.Range and so we can use range based algorithms.
Upvotes: 2
Reputation: 179779
The reason is that the designers of the STL came from the theoretical side. In Comp.Sci, a range is a more fundamental concept than a container. In practice, containers are far more common. The STL was added under serious time pressure back in 1996-1998, and it wasn't refactored aggressively.
You see a similar problem with the third argument of std::for_each
. In theory, lambda's exist. In C++98, they didn't. Therefore you had to either define a functor out of line, use a verbose syntax with binders and compositors, or use a stateless pointer-to-function.
Personally, I think all STL algorithms should have taken a range (single object), not a pair of iterators. It's trivial to wrap the latter in a range. Now you see that ostream_iterators have to define a rather arbitrary end object.
Upvotes: 1
Reputation: 137770
I'm totally ga-ga for STL, absolutely. But I can't recall actually ever using for_each
.
The idiom is
for ( container::iterator it = coll.begin(); it != coll.end(); ++ it )
C++11 introduces sugar to reduce this to
for ( auto elem : coll )
This is similar to your convenience function, but uses free (non-member) std::begin
and std::end
functions that allow compatibility with objects that are not standard containers.
Also, looking it up (I haven't gotten to play with this as it's not in GCC yet), it looks like it restricts the programmer to accessing elements of the range, not iterators into it.
As for using the container to refer to the entirety of its range, it's much preferable to maintain the flexibility to allow subranges. The alternative solution is to introduce an idiom for a pair of iterators, { begin, end }
. There was some debate, and I expected C++11 to include a feature such that
begin( make_pair( begin_, end_ ) ) // == begin_,
end( make_pair( begin_, end_ ) ) // == end_,
for ( auto elem : make_pair( begin_, end_ ) ) // iterates over [ begin_, end )
but upon reading the Standard it looks like pair
lacks this functionality.
You can create your own such pair
, however, to obtain the flexible range-based for
:
template< typename iter >
struct range_type {
iter first, last;
// use friends because Standard specifies these should be found by ADL:
friend iter begin( range_type const &r ) { return r.first; }
friend iter end( range_type const &r ) { return r.last; }
};
template< typename iter >
range_type< iter > range( iter first, iter last )
{ return range_type< iter >{ first, last }; }
// usage:
for ( auto elem : range( begin_, end_ ) ) // iterates over [ begin_, end )
Upvotes: 5
Reputation: 3596
There is nothing wrong with what you suggest, though I'd have to say that Boost.ForEach is about as close to "idiomatic" as things get in the present-day C++ universe.
The benefit of this approach, which in use looks something like:
BOOST_FOREACH(value_type i, collection) {
function(i);
}
is that you can also inline your operations and not be bound strictly to an explicit functional mapping.
Upvotes: 1