How to avoid lots of ugly pointer casting when using a container in C

ccoding-stylepointerstype casting

Let's say I have a container in C, for example something similar to C++' std::deque:

struct deque
{
    // blah
};

struct deque* deque_create(size_t element_size, size_t init_deq_size);
void* deque_get(struct deque const* deq, size_t index);
void* deque_push_back(struct deque* deq, void* elt);
void deque_pop_front(struct deq* deq);
//etc.

All of this works rather nice if the vector stores objects themselves rather than pointers to objects:

int* i = deque_get(deq, 5);

Still some explicit casting and nesting parentheses sometimes seems necessary:

for(size_t i = 0; i < deque_size(deq); i++) {
    (*(int*)deque_get(deq, i))++;
}

I find it ugly to have to write (*(int*)deque_get(deq, i))++; where in C++ I'd be able to just write deq[i]++;.

However, the ugliness seems to go off-charts when this container is used to store pointers to objects.

For example, let's assume we are storing pointers to this struct:

struct foo
{
    int bar;
    double baz;
};

Then getting the bar field of the fifth element of our container would look like this:

(*(struct foo**)deque_get(deq, 5))->bar;

Or like this:

struct foo** foo = deque_get(deq, 5);
(*foo)->bar;

Or like this:

struct foo* foo = *(struct foo**)deque_get(deq, 5);
foo->bar;

(Whereas in C++ we'd only have to write deq[5]->bar).

But nothing beats in ugliness checking if an element is not null and if so, if its bar field is >= 5:

if(*(struct foo**)deque_get(deq, i) != NULL &&
        (*(struct foo**)deque_get(deq, i))->bar >= 5) {
    // do_something
}

In short: When I use containers in C, I tend to write lots of ugly code consisting of lots of casts and parentheses, a problem that can be somewhat mitigated by adding unecessary lines of code – a solution that is pretty inconvenient if the need to resort to it arises frequently.

How to write such code readably and concisely?

Best Answer

If you want your code to be more readable, then make it more verbose so that the intent is obvious and concrete!

For example, your first loop would be much more readable (at least to some folks) if it was written like this:

for (i = 0; i < deque_size(deq); i++) {
    int *x;

    x = deque_get(deq, i);
    *x++;
}

This is of course a somewhat contrived example and I personally don't find somewhat isolated uses of expressions such as (*(int *) deque_get(deq, i))++ to be too ugly. I wouldn't use a macro though -- they can lead to too many hard-to-find bugs once they get a tiny bit more complicated.

In any case, don't try to put too much into any given expression. Be explicit about your intent by writing it out in full. Any decent optimizing compiler will generate the same code from this as it would from the way you wrote it.

I don't understand why you are storing pointers to pointer to foo (i.e. struct foo **) in your second example though. Don't do that. You never want to manipulate those pointers in the same way you might manipulate an int object, for example, and you would also need somewhere else to store the intermediate pointer to foo pointers. Just store struct foo * and then you can write:

struct foo *x;

x = deque_get(deq, i);
if (x && x->bar >= 5) {
    // do_something
}

Macros can help though if they're well designed and named. Take a look at the queue.h macros available by default in many systems, e.g. all BSD and Linux systems: QUEUE(3)