Reputation: 94329
A part of the source code for a project I'm working on, which is responsible for compressing a sequence of 'events', looks like this:
#include <iterator>
#include <list>
typedef int Event;
typedef std::list<Event> EventList;
struct Compressor {
// Returns an iterator behind the last element which was 'eaten'
virtual EventList::const_iterator eatEvents( const EventList &l ) = 0;
};
// Plenty of Compressor subclasses exist
void compressAndCopyEatenEvents( Compressor &c ) {
EventList e;
e.push_back( 1 );
EventList::const_iterator newEnd = c.eatEvents( e );
EventList eatenEvents;
std::copy( e.begin(), newEnd, std::back_inserter( eatenEvents ) ); // barfs
}
The issue here is that the compressAndCopyEatenEvents
function has a non-const list of events; this list os passed to the eatEvents
methods, which takes a reference-to-const and yields a const_iterator
. Now the compressAndCopyEatenEvenst
function would like to copy the range of eaten events away, so it decides to use some algorithm (std::copy
here, which of course could just as well be replaced with the right std::list
constructor call - the point is that this problem exists with all kinds of ranges).
Unfortunately(?) many (if not all?) ranges need to be composed from the same iterator type. However, in the above code, 'e.begin()' yields an EventList::iterator
(because the object is not const) but 'newEnd' is an EventList::const_iterator
.
Is there a design weakness here which causes this mess? How would you tackle it?
Upvotes: 2
Views: 1674
Reputation: 11787
See what the master says:
Scot Meyers in Effective STL
Item 26. Prefer iterator to const iterator, reverse_iterator, and const_reverse_iterator. Though containers support four iterator types, one of those types has privileges the others do not have. That type is iterator, iterator is special.
typedef deque<int> IntDeque; //STL container and
typedef lntDeque::iterator Iter; // iterator types are easier
typedef lntDeque::const_iterator ConstIter; // to work with if you
// use some typedefs
Iter i;
ConstIter ci;
… //make i and ci point into
// the same container
if (i == ci ) ... //compare an iterator
// and a const_iterator
Item 27. Use distance and advance to convert a container's const_iterators to iterators.
typedef deque<int> IntDeque; //convenience typedefs
typedef lntDeque::iterator Iter;
typedef lntDeque::const_iterator ConstIter;
ConstIter ci; // ci is a const_iterator
…
Iter i(ci); // error! no implicit conversion from
// const_iterator to iterator
Iter i(const_cast<Iter>(ci)); // still an error! can't cast a
// const_iterator to an iterator
What works is advance and distance
typedef deque<int> IntDeque; //as before
typedef IntDeque::iterator Iter;
typedef IntDeque::const_iterator ConstIter;
IntDeque d;
ConstIter ci;
… // make ci point into d
Iter i(d.begin()); // initialize i to d.begin()
Advance(i, distance(i, ci)) //move i up to where ci is
// (but see below for why this must
// be tweaked before it will compile)
Upvotes: 1
Reputation: 18964
You could add a second overload of eatEvents, so the compiler will automatically pick the right one so as to preserves const-ness:
virtual EventList::iterator eatEvents( EventList &l ) = 0;
(One or both of them could be non-virtual and implemented in terms of a single underlying function.)
Sometimes that works well, although I'm not convinced it's the perfect thing here.
Upvotes: 0
Reputation: 133004
In C++03 the only possible way is to cast. (which is ugly, which is a design flaw, yes).
std::copy( static_cast<EventList::const_iterator>e.begin(), newEnd, std::back_inserter( eatenEvents ) );
or have another named variable:
EventList::const_iterator constBegin = e.begin();
std::copy(constBegin , newEnd, std::back_inserter( eatenEvents ) );
In C++11 you have cbegin
and cend
functions (that always return const_iterator
s) so you'd do simply
std::copy( e.cbegin(), newEnd, std::back_inserter( eatenEvents ) );
Upvotes: 4
Reputation: 161
Consider using
EventList::const_iterator b = e.begin();
std::copy( b, newEnd, std::back_inserter( eatenEvents ) );
This will cause the correct list::begin()
overload to be called, and for std::copy
to compile cleanly.
Upvotes: 2