Following the recent synchronisation of all of the collection cl****es' function syntax, I have written some very simple function macros to make code for iterating through vectors a little easier. They are written as function macros not methods of the collection cl****es because of the need to specify a specific index for each iteration. They enable the following code:
for(unsigned short i=0; i<last_built.get_count(); i++ ) {
grund_t* gr = welt->lookup(last_built[i]);
...
to be replaced with code like this:
ITERATE(last_built,i)
{
grund_t* gr = welt->lookup(last_built[i]);
...
The function macros work with any collection cl**** that has two properties: (1) a "get_count()" method, that returns a 1 based index representing the number of elements currently contained in the collection; and (2) an overloaded [] operator allowing access to individual elements using a 0 based index. Here is the code for the function macro, which should be inserted at the top of the file of every collection cl**** with those properties:
#ifndef ITERATE
#define ITERATE(collection,i) for(uint16 i = 0; i < collection.get_count(); i++)
#endif
#ifndef ITERATE_PTR
#define ITERATE_PTR(collection,i) for(uint16 i = 0; i < collection->get_count(); i++)
#endif
If it would be helpful, I could try to make up a patch file against the standard version of Simutrans (they are currently integrated into Simutrans-Experimental) for easy integration into the trunk. These macros are helpful in that: (1) they reduce the time that it takes to write iteration loops for vector type collections; and (2) they reduce the possibility of errors such as the following:
for(unsigned short i = last_built.get_count(); i <= 0; i-- ) {
grund_t* gr = welt->lookup(last_built[i]);
...
Any comments would be appreciated :-)
just curious: would that be possible with templates instead of macros?
I think you have to put the first arguments into brackets for more safety:
#ifndef ITERATE
#define ITERATE(collection,i) for(uint16 i = 0; i < (collection).get_count(); i++)
#endif
#ifndef ITERATE_PTR
#define ITERATE_PTR(collection,i) for(uint16 i = 0; i < (collection)->get_count(); i++)
#endif
Maybe you could also backup the count of the vector like this (but I think there's no easy way to avoid conflicts of the max-variable):
#define ITERATE(collection,i) uint16 max = (collection).get_count(); for(uint16 i = 0; i < max; i++)
I think, I wouldn't like it. A for-loop is more readable than such a macro.
Then you could use the std-like iterator templates in there anyway. That way you could easily switch between vectors and lists. Moreover they are save for deletion etc. which macros are not.
You could then make an iterator macro that automatically iterates either list or vectors.
Gerw,
would there ever be a situation in which something not in brackets could be p****ed to the macro that (1) works if it is in brackets; and (2) does not work if it is not? I cannot immediately imagine such a case, as the macro is defined only for template cl****es, where there would just be the name of the template object p****ed, but perhaps you have thought of something that I have not...?
As to the saving of the count variable, that is not a bad idea, although it does cause safety problems. Not so much that "max" might conflict with something, as one could choose a keyword that is very unlikely to do so (such as "MACRO_ITERATOR_COLLECTION_TOTAL_COUNT_SIZE"), but that, if the operation involves a deletion of one of the elements, the change in the count of the collection would not be registered, and an out of range error would be produced as for loop would try to access an element beyond its range.
Dwachs and Prissi,
the reason that templates are not used is because it would not produce the same result; one would not be able easily to access
collection[i]->some_function();
(for example) with so few lines of code. One would have to use, as Prissi suggests, an iterator; but I find iterators cumbersome to code, and, as I understand things, are slower to execute, since they require additional function calls and objects, whereas a straight for loop is very simple and accesses the collection directly. Having first to declare an iterator and then advance it is more work both to code and execute than a simple loop, especially, in the case of coding time, where it is given a function macro.
I had the idea for this macro from the
foreach keyword in C#, the functionality of which this macro is designed to emulate as much as a C++ function macro sensibly can. In C#, one can write something like this:
foreach(object i in collection)
{
i.some_function();
}
The idea is that the ITERATE macro allows a very similar result: compare the above to this example:
ITERATE(i,collection)
{
collection[i]->some_function();
}
The readability comes in part from the similarity to C#'s
foreach keyword.
But I'm interested in Prissi's suggestion of an iterator macro; might I ask: how would that work exactly?
you can have a foreach macro with an iterator.
#define FOREACH(typ,basis,iter) for (typ::iterator iter = basis.begin(); iter != basis.end(); ++iter)
and you access i-> or (*i). all desired operators.
This works FOREACH(slist_tpl<fabrik_t *>, fablist, fab ) als well as for FOREACH(vector_tpl<fabrik_t *>, fablist, fab )
One could also think to have and end_iter=basis.end() define once and then iterate without rechecking each time; but this way it is saver.
However, I strongly suggest not using macros for lasy typing un such cases. This easily causes errors that are hard to catch later.
Just food for thought...
http://www.boost.org/doc/libs/1_38_0/doc/html/foreach.html
(for those who care about this)
Prissi,
that could work for iterators - but would lose the speed advantage of a for loop. Interesting, though.
Those iterators are as fast as the normal for loop, since the just return pointers and compare pointers. Since the std uses also such iterators, compiler are nowadays very much trimmed to optimize those.
And the boost macro works only on std and not on simutrans marcos I think. (Apart from the fact that boost is a pain to set up on some achritectures and makes compilation more difficult for many people.)
Ahh, interesting - I always thought that iterators were slower. Don't they require more memory because they create a special iterator object? If they really are just as fast, your FOREACH macro is very interesting indeed.
Maybe some fancy constructs like
ITERATE_PTR(collection+4,i)
where collection is a pointer to an array of vectors.
Couldn't one just write
ITERATE_PTR((collection+4),i)
?
Of course. But it's safer (and more programmer friendly) when you add the brackets to the macro definition.
Ahh, yes, I see. The change suggested will be incorporated into the next version of Simutrans-Experimental.