Consider some C code like this:

    struct Foo
    {
        struct Foo *next;
        int bar;
    };

    struct Bar
    {
        struct Bar *next;
        char* name;
    };

    struct Unrelated
    {
        int x;
        int y;
    };

    void combulate(void *item)
    {
        struct ItemWithNext
        {
            struct ItemWithNext *next;
        };

        struct ItemWithNext *inext = item;

        // ... lots of code here
        inext->next = ...;
    }

    int main()
    {
        struct Foo foo;
        struct Bar bar;
        struct Unrelated unrelated;
        combulate(&foo);
        combulate(&bar);
        combulate(&unrelated); // bug
    }

The function `combulate` must use a `void*` parameter to accept any argument that structurally conforms to the interface it expects -- in this case, having a `next` field as its first member. That has the disadvantage that the type system doesn't catch the mistake of passing it an Unrelated value, which doesn't conform to the expected binary interface. In D we might instead do something like this:

    struct Foo
    {
        Foo* next;
        int bar;
    }

    struct Bar
    {
        Bar* next;
        char* name;
    }

    struct Unrelated
    {
        int x;
        int y;
    };

    void combulate(T)(T* item)
    {
        // ... lots of code here
        item.next = ...;
    }

    void main()
    {
        Foo foo;
        Bar bar;
        Unrelated unrelated;
        combulate(&foo);
        combulate(&bar);
        combulate(&unrelated); // compilation error
    }

This catches the bug, but will have the disadvantage of generating code for the various types to which combulate is specialized, even though the body of the function template doesn't rely on anything that varies between the specializations. That is problematic in the context where I'm using this (embedded systems). So instead I've started using a mixed approach, with generic code that checks for the appropriate type, but delegates the actual work to a non-templated function (a fully specialized function template in my actual case), except in the cases where the actual code is small enough or the specialization significantly improves the performance. Something like this:

    private struct ItemWithNext
    {
        ItemWithNext *next;
    }

    private void combulate(ItemWithNext* item)
    {
        // ... lots of code here
        item.next = ...;
    }

    pragma(inline, true)
    void combulate(T)(T* item)
        if(someKindOfCheck!item)
    {
        combulate(cast(ItemWithNext*) item);
    }

So far this seems to be working well for me. Do you have experience writing this kind of code? Do you have any advice that might be relevant to this situation?

PS: I know I don't have to define and use a structure to access the next field, but I feel like that generalizes better to other scenarios and is clearer.

Reply via email to