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