-
If anyone ever wants to compare structs using the method I described above, here's some code that actually compiles and tests as far as the testcases here allow:
Code:
#include <string.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>
typedef enum { VOID = 0, INT, CHARARRAY, CHAR, CHARPTR, FLOAT, DOUBLE, STRUCT, STRUCTPTR } EType;
typedef ptrdiff_t offset_t;
typedef struct SElement
{
char *name; // Not actually needed - just because the macro CAN DO THAT.
offset_t offs;
enum EType type;
} Element;
typedef struct SExample
{
int a;
char *str1;
char str2[40];
char b;
} Example;
#define ELEM(structname, name, type) { #name, offsetof(structname, name), type }
Element exampleDescr [] =
{
ELEM(Example, a, INT),
ELEM(Example, str1, CHARPTR),
ELEM(Example, str2, CHARARRAY),
ELEM(Example, b, CHAR)
};
typedef struct SExample2
{
int a;
float f;
double d;
char b;
} Example2;
Element example2Descr[] =
{
ELEM(Example2, a, INT),
ELEM(Example2, f, FLOAT),
ELEM(Example2, d, DOUBLE),
ELEM(Example2, b, CHAR)
};
int CompareStructsGeneric(void *struct1, void *struct2, Element *descr, size_t nElements)
{
size_t i;
for(i = 0; i < nElements; i++)
{
void *elem1, *elem2;
int cmp;
char *s1, *s2;
double d1, d2;
elem1 = (char *)struct1 + descr[i].offs;
elem2 = (char *)struct2 + descr[i].offs;
switch(descr[i].type)
{
case INT:
if (*(int *)elem1 > *(int *)elem2)
return 1;
else if (*(int *)elem1 < *(int *)elem2)
return -1;
break; /* Equal, continue to next element ... */
case CHAR:
if (*(char*)elem1 > *(char*)elem2)
return 1;
else if (*(char*)elem1 < *(char*)elem2)
return -1;
break; /* Equal, continue to next element ... */
case FLOAT:
d1 = *(float *)elem1;
d2 = *(float *)elem2;
// Avoid code duplication at expense of using goto!
goto fcompare;
case DOUBLE:
d1 = *(double *)elem1;
d2 = *(double *)elem2;
fcompare:
if (d1 > d2)
return 1;
else if (d1 < d2)
return -1;
break;
case CHARPTR:
elem1 = *(char **)elem1;
elem2 = *(char **)elem2;
// Clever-tricks: fall-throug!
case CHARARRAY:
s1 = elem1;
s2 = elem2;
cmp = strcmp(s1, s2);
if (cmp) return cmp;
break;
default:
printf("Incorrect or unimplemented case: %d\n", descr[i].type);
break;
}
}
return 0;
}
Example a = { 1, "Hello", "World!", 'a' };
Example b = { 2, "Hello", "World!", 'b' };
Example c = { 1, "Hello", "World!", 'a' };
Example d = { 1, "Hello", "World!", 'd' };
Example2 a2 = { 1, 5.0f, 6.0, 'a' };
Example2 b2 = { 1, 5.1f, 6.0, 'a' };
Example2 c2 = { 1, 5.0f, 6.0-0.001, 'a' };
struct TestCase
{
char *name;
void *s1, *s2;
Element *descr;
size_t nelem;
int res;
};
#define ARRAYSIZE(x) (sizeof(x)/sizeof((x)[0]))
#define TC(name, s1, s2, ds, res) { #name, &s1, &s2, ds, ARRAYSIZE(ds), res }
struct TestCase testCases[] =
{
TC(equal1, a, a, exampleDescr, 0),
TC(equal2, a, c, exampleDescr, 0),
TC(greater1, b, a, exampleDescr, 1),
TC(lesser1, a, b, exampleDescr, -1),
TC(equal3, a2, a2, example2Descr, 0),
TC(equal3, a2, a2, example2Descr, 0),
TC(greater2, b2, a2, example2Descr, 1),
TC(lesser2, c2, a2, example2Descr, -1),
TC(lesser3, a, d, exampleDescr, -1)
};
void expect(const char *name, int expected, int res)
{
if (res != expected)
{
printf("%s: Expected %d, got %d\n", name, expected, res);
}
}
int main()
{
int i;
for(i = 0; i < ARRAYSIZE(testCases); i++)
{
int res;
printf("Performing test-case %s\n", testCases[i].name);
res = CompareStructsGeneric(testCases[i].s1, testCases[i].s2, testCases[i].descr, testCases[i].nelem);
expect(testCases[i].name, testCases[i].res, res);
}
return 0;
}
Note, I know goto and fall-through switches is bad. Doing lots of duplication of code is ALSO bad - it's a balance, and I'm not feeling like typing a lot more than necessary, so went for the first nasty variant rather than the second one. [All of the non-string comares could also be turned into a multiline macro, but I do not like multiline macros much, so since it's actually OK to write it out, I did].
--
Mats