In c++, the * operator can be overloaded, such as with an iterator, but the arrow (->) (.*) operator does not work with classes that overload the * operator. I imagine that the preprocessor could easily replace all instances of -> with (*left).right, and that would make iterators nicer to implement. is there a practical reason for -> to be different, or is that just a peculiarity of the language/designers?
C++ – Why Isn’t the Arrow Operator Just an Alias of *?
coperators
Related Solutions
There are two diametrically opposed schools of thought in programming language design. One is that programmers write better code with fewer restrictions, and the other is that they write better code with more restrictions. In my opinion, the reality is that good experienced programmers flourish with fewer restrictions, but that restrictions can benefit the code quality of beginners.
User-defined operators can make for very elegant code in experienced hands, and utterly awful code by a beginner. So whether your language includes them or not depends on your language designer's school of thought.
Been there, got burned. Creating things that look like iterator but have different or extra requirements will lead to a mess. Basically many ranges are not copyable or at least not cheaply so, but that's what one normally expects of an iterator (it is a requirement of the iterator concept).
You should not have iterators that know of their own end. But chaining works with ranges. There are two ways to define ranges:
As forward-iterable "containers", which you can make of simple pair of iterators. This is a C++ way (and Boost.Range1 has some useful utilities for these), but sometimes it is quite a bit of extra work to make various objects that provide sequences fit the interface.
Define your interface for "generators". It will probably be similar to the python one, but since exceptions are less convenient in C++ than python, it will probably have different method of detecting end. I settled for following interface for my own needs2:
template <typename T> concept Generator { bool valid(); void next(); T get(); };
where the iteration looks like:
while(g.valid()) { auto item = g.get(); do_anything_with(item); g.next(); }
the generator conceptually starts on first item in the sequence, but may only be accessed after
valid
is called. I found this allows distributing the hard work between constructor,valid
andnext
as is fit for each case and it can be easily wrapped in iterator similarly to howistream_iterator
is done. Other variations of the interface are possible including following theistream
one (but it has disadvantage that it returns default element when the iteration fails).
Basically you should probably combine the approaches. If you use the later concept, you can adapt any such implementation to fit the (quite complex) Range concept from Boost.Range e.g. using "mixin" and Curiously Recurring Template Pattern. Something like:
template <typename GeneratorT, typename ValueT>
class GeneratorIterator :
boost::iterator_facade<GeneratorT, ValueT, boost::single_pass_traversal_tag> {
GeneratorT *g;
GeneratorIterator() : g() {}
GeneratorIterator(GeneratorT *g) g(g) {}
ValueT &dereference() {
if(!g || !g.valid())
throw std::runtime_error("...");
return g->get();
}
bool equal(GeneratorIterator const &l) {
return g == l.g || ((!g || !g.valid()) && (!l.g || !l.g.valid()));
}
void increment() {
if(g)
g.next();
}
}
template <typename GeneratorT, typename ValueT>
class GeneratorFacade {
public:
typedef GeneratorIterator<GeneratorT, ValueT> iterator;
typedef GeneratorIterator<GeneratorT, ValueT> const_iterator;
const_iterator begin() const {
return const_iterator(this);
}
const_iterator end() const {
return const_iterator();
}
}
The advantage of the indirection is that the ranges now don't have to be copyable at all or not cheaply while the iterator is just a pointer and therefore is cheaply copyable as required. And defining generators is simple and easy to understand while they still end up conforming to the hairy standard C++ interface.
(Disclaimer: I wrote it off top of my head, not tested)
1 Boost.Range includes concatenating ranges. Don't reinvent the wheel and reuse or at least inspire yourself.
2 The Iterators Must Go talk linked in Ylisar's answer comes up with the same interface, just different names. Note that many languages combine the next
/popFront
and valid
/empty
to one next
that returns a boolean, but that approach is much more difficult to wrap in iterators and conceptually somewhat more complex, because then the iterators start out in special "uninitialized" state.
Best Answer
The rule that
foo->bar
equals(*foo).bar
only holds for the builtin operators.Unary
operator *
doesn't always have the pointer dereference semantics. I could make a library in which it means matrix transposition, zero-or-more parser matches, or pretty much anything.It would make the language more bothersome if anything that overloads unary
operator *
would suddenly gain anoperator ->
you did not ask for, with semantics that might not make sense.operator ->
is separately overloadable, so if you want one, you can overload one with minimal effort.Also note that such an overload would have some rather interesting properties, like automatically chaining
operator ->
calls until one in the chain returns a raw pointer. This is quite useful for smart pointers and other proxy types.