RFC: generic safety, specialized implementation?

Luís Marques luis at luismarques.eu
Fri Jan 19 19:18:22 UTC 2018


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.


More information about the Digitalmars-d mailing list