C abuse of the week

Mar 17, 2011 10:23


A while back I wanted to write some C code that looped over every element in a collection. But I wanted the code to be parametrisable in the face of different implementations of that collection, because it might have to link against several other pieces of code which might all store the things in question in a different format.

iteration over a collection of elephants by C switch abuse )

Leave a comment

Comments 22

andrewducker March 17 2011, 10:48:16 UTC
I just wrote a few paragraphs on why this kind of code would make me tear my hair out.

And then realised that you were working in C, not C++, and thus objects and interfaces were not an option for you!

And that made me remember why I like OOP so much.

Reply

simont March 17 2011, 10:55:31 UTC
OOP only solves this problem if you can arrange that every possible type of container that the implementation might be using can be persuaded to support the same iterator mechanism, so that you can put a fixed for statement at the loop site and just vary the iterator implementation per container.

But I think that even if I'd been able to guarantee the availability of C++, it's not clear to me in this context that the various implementations would have been set up that way - they're all in code bases maintained by different groups of people who have no incentive to cooperate on issues like this. So it's quite likely that I'd still have needed some sort of macro-based parametrisation at the call site, even if it was less overwhelming than what I've got here.

Reply

andrewducker March 17 2011, 11:03:48 UTC
Yeah - this is much easier in C#, where all of the built in collection types support IEnumerable, and if you were creating a new collection type then not supporting IEnumerable would be a bit silly.

(And yes, I know that C# isn't suitable for all sorts of things.)

Reply

simont March 17 2011, 11:08:24 UTC
Does even the ordinary array type support IEnumerable too? (I presume that C# still has ordinary array types, and doesn't actually require you to instantiate a dynamically allocated collection object every time you want to store n things in a row.)

Reply


atheorist March 17 2011, 15:07:10 UTC
Do you prefer the switch to a flag like this?

int specialfirsttimeflag;
for (elephant = head, specialfirsttimeflag= 1;
elephant != head && specialfirsttimeflag--;
elephant = elephant->next) {

Or you can't because it might conflict with a name outside the macro?

Reply

simont March 17 2011, 15:19:49 UTC
There are two problems with encapsulating that version in my LOOP_OVER_ALL_ELEPHANTS macro. One is that it contains two separate statements, so it breaks syntactic indivisibility in cases like
if (condition)
LOOP_OVER_ALL_ELEPHANTS(elephant) {
/* ... */
}Admittedly you might reasonably feel that I should just avoid calling the macro in any circumstances like that, which would be a fair enough position, but I note that all the other expansions discussed here (including the evil switch one) do work in that context.

But the other, more important, reason is that it has a declaration in it, and in C90 declarations can't be interleaved with code. (For annoying reasons, the code in which I was using this macro is required to compile cleanly in C90 as well as C99.) So if I put any other statement before calling LOOP_OVER_ALL_ELEPHANTS, the declaration of specialfirsttimeflag would become illegal.

Reply

cartesiandaemon March 17 2011, 21:13:29 UTC
I was trying an alternative solution. I think you may be able to fix the problems this approach has with not quite being semantically identical to a "for" block, but I couldn't overcome the restrictions on not being able to declare another variable.

You could possibly wrap up the "compare elephant and head, except the first time, then just return true" logic into a function with a static variable in, but it would mean the code would be definitely non-reentrant. (Unless your preprocessor did something clever with the LINE directive in the macro, but I can't remember, that wasn't in C90 either, right?)

Reply

simont March 18 2011, 09:12:05 UTC
No, __LINE__ is perfectly good C90. (Or else I wouldn't have used it in my coroutines trick - my original invention of that predated C99 by several years, and Tom Duff said he'd thought of the same thing much, much earlier.)

I don't see how it helps you here, though. It surely can't save you from thread-safety issues, because it won't have a distinct value in each thread?

Reply


gerald_duck March 17 2011, 17:24:04 UTC
Seriously, seriously, I know there's lots to hate in C++, starting with Bjarne Stroustrup having been born and working through to C++0x's lambda function syntax, but generic programming is one thing it gets more right than any other language I've seen.

The STL iterator concepts are a well thought out framework for generic programming.

You can write code like this:

for (Elephant::iterator i=elephant.begin(); i!=elephant.end(); ++i) {
// loop body executed once for each elephant
}

…and it works regardless of the type of elephant. In C++0x, it gets even simpler:

for (auto &e: elephant) {
// loop body executed once for each elephant
}

The iterator for each container type deals with the specifics of stepping through the elements of an array, a linked list, a circularly linked list, a balanced tree, a hash table, a readdir(), a circular buffer, the characters of a UTF-8 string… whatever. Because the code is generic rather than run-time polymorphic, the optimiser is told precisely what's going on for a particular use of the ( ... )

Reply

nunfetishist March 17 2011, 18:31:11 UTC
Actually, C++ is a real ballache on a $3 MCU :)

Reply

gerald_duck March 18 2011, 19:45:11 UTC
Howso? What do you need for C++ that you don't need for C? OK, there's plenty it's nice to have for C++ - a terminate handler, some typeinfo structures, perhaps a library of two - but none are essential, surely?

Reply

cartesiandaemon March 17 2011, 19:20:57 UTC
Yeah, this.

(Although I'm unsurprised Simon's effectively unable to change the codebase, to me something like this is a BIG FLAG that switching to a more expressive language is long overdue.)

Reply


cartesiandaemon March 17 2011, 19:23:59 UTC
I'm quite impressed.

Although honestly, while I love the tweaks, I think for all practical purposes, making it clear that LOOP_OVER_ALL_ELEPHANTS can't substitute in as an arbitrary statement is fine, it's clearly magic, you shouldn't TRY to use it in a more sophisticated context. (Also, pretty much always put braces after ifs and loops, unless the loop is a single short statement on the same line as the if, and even then I very rarely find I want to.)

Reply


Leave a comment

Up