cgenericscompilationintrusive-containers

Is this header a good use case for `_Generic` in C?


I am writing a library of intrusive data structures in C. Many of these containers will share functionality. For example, there will be many containers that offer forward and reverse iteration. So, I make a header that will provide these generic operations as macros across all containers. Here is just the forward iterator.

#include "double_ended_priority_queue.h"
#include "ordered_map.h"
#include "realtime_ordered_map.h"

#define BEGIN(container_ptr)                                          \
    _Generic((container_ptr),                                         \
        double_ended_priority_queue *: depq_begin,                    \
        ordered_map *: om_begin,                                      \
        realtime_ordered_map *: rom_begin)(container_ptr)

#define NEXT(container_ptr, iter_elem_ptr)                            \
    _Generic((container_ptr),                                         \
        double_ended_priority_queue *: depq_next,                     \
        ordered_map *: om_next,                                       \
        realtime_ordered_map *: rom_next)((container_ptr),            \
                                          (iter_elem_ptr))

Usage then looks like this.

struct val
{
    int id;
    int val;
    depq_elem elem;
};

void
inorder_fill(int vals[], size_t size, double_ended_priority_queue *pq)
{
    if (depq_size(pq) != size)
    {
        return;
    }
    size_t i = 0;
    for (struct val *e = BEGIN(pq); e && i < size; e = NEXT(pq, &e->elem))
    {
        vals[i++] = e->val;
    }
}

This seems nice and such functionality could be extended for many other operations (emplace, Rust's Entry API, ranges, etc.). However, I must include the headers for all of the containers in the _Generic list at the top of the file. Otherwise, when the user includes the generics.h file they will receive a compilation error with unknown types for all the type cases in the _Generic they have not included.

The number of includes in the generics.h file will grow greatly as more containers are implemented that share these functionalities. While there is no runtime overhead for the _Generic usage, I seem to be creating huge compilation time bloat with this approach.


Solution

  • I'd say the code is pretty much ok.

    Perhaps instead of having some mysterious BEGIN/NEXT macros, maybe consider placing the whole loop in a function to make the code more readable.

    In case the functions are already in place and can't be modified, then consider wrapper functions (which will get inlined/optimized out).

    That is, make a macro like container_iterate() and that macro checks the type of container with _Generic, then calls for example depq_iterate which is a wrapper function containing the for loop and the calls to depq_begin/depq_next.

    As for maintaining a lot of macros as you add more types - well there's always X macros which among other things can be used to generate the _Generic association list, or even function templates. They increase maintainability at the expense of readability. But perhaps that's material for a separate question.