as I initially understood the code, the following code dispatches to several functions:
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#define CHECK(expr, ...) do { \
if (!(expr)) { \
fflush(0); \
fprintf(stderr, "ERROR: %s:%d: expression failed: %s", __func__, __LINE__, #expr); \
__VA_OPT__(fprintf(stderr, ": " __VA_ARGS__);) \
fprintf(stderr, "\n"); \
exit(1); \
} \
} while(0)
// method library
// maximum number of arguments we support
#define MAXARGS 16
// types
enum type {
VOID = 0,
INT,
FLOAT,
};
const char *type2str(enum type type) {
switch (type) {
case VOID: return "void";
case INT: return "int";
case FLOAT: return "float";
}
return "unknown";
}
// function to call
struct method {
enum type arg[MAXARGS];
void (*m)();
};
// an array of overloaded functions
struct methods {
int len;
struct method *m;
};
// argument to pass
struct arg {
enum type type;
union {
float f;
int i;
} v;
};
// create an argument
struct arg mk_arg_i(int i) {
return (struct arg){INT, .v.i=i};
}
struct arg mk_arg_f(float f) {
return (struct arg){FLOAT, .v.f=f};
}
#define mk_arg(x) _Generic((x) \
, int: mk_arg_i \
, float: mk_arg_f \
, double: mk_arg_f \
)(x)
// call function with given arguments
void method_call(struct method t, struct arg a[MAXARGS]) {
switch (t.arg[0] + t.arg[1] * 10) {
case VOID:
CHECK(a[0].type == VOID);
CHECK(a[1].type == VOID);
((void(*)())t.m)();
return;
case INT:
CHECK(a[0].type == INT);
CHECK(a[1].type == VOID);
((void(*)(int))t.m)(a[0].v.i);
return;
case FLOAT:
CHECK(a[0].type == FLOAT);
CHECK(a[1].type == VOID);
((void(*)(float))t.m)(a[0].v.f);
return;
case INT + FLOAT * 10:
CHECK(a[0].type == INT);
CHECK(a[1].type == FLOAT);
((void(*)(int, float))t.m)(a[0].v.i, a[1].v.f);
return;
}
CHECK(0, "f(%d, %d) with %d %d",
t.arg[0], t.arg[1], a[0].type, a[1].type);
}
// from an array of functions call first matching function that matches arguments
void method_dispatch(struct methods t, struct arg a[MAXARGS]) {
for (struct method *end = t.m + t.len, *m = t.m; m != end; m++) {
int same = 1;
for (int i = 0; i < MAXARGS; ++i) {
same &= m->arg[i] == a[i].type;
}
if (same) {
method_call(*m, a);
return;
}
}
CHECK(0, "MethodError: Could not dispatch %s %s",
type2str(a[0].type), type2str(a[1].type));
}
// cool overload on number of args to hide boilerplate
#define dispatch_1(t, a) method_dispatch(t, (struct arg[MAXARGS]){mk_arg(a)})
#define dispatch_2(t, a, b) method_dispatch(t, (struct arg[MAXARGS]){mk_arg(a), mk_arg(b)})
#define dispatch_N(_2,_1,N,...) dispatch##N
#define dispatch(t, ...) dispatch_N(__VA_ARGS__,_2,_1)(t,__VA_ARGS__)
// language
void f_i(int x) {
printf("x::Int = %d\n", x);
}
void f_f(float x) {
printf("x::Float = %g\n", x);
}
void f_i_f(int x, float y) {
printf("x::Int = %d ; y::Float=%g\n", x, y);
}
struct methods f = {
3,
(struct method[]) {
{ { INT }, (void(*)())f_i, },
{ { FLOAT }, (void(*)())f_f, },
{ { INT, FLOAT }, (void(*)())f_i_f, },
},
};
int main() {
dispatch(f, 5); // prints "x::Int = 5"
dispatch(f, 6.5); // prints "x::Float = 6.5"
dispatch(f, 7, 8.5); // prints "x::Int = 7 ; y::Float = 8.5"
dispatch(f, 9.5, 10); // # throws a MethodError
}
Which prints:
x::Int = 5
x::Float = 6.5
x::Int = 7 ; y::Float=8.5
ERROR: method_dispatch:110: expression failed: 0: MethodError: Could not dispatch float int
But looking at your code, you are happy with writing serializer/deserializer of your data to a chosen common storage - like uintptr_t. Bottom line, this is similar to int main(int argc, char *argv[]) , just instead of char * you use your own datatype that can be converted to your chosen subset of types - int or float. The following code works on arbitrary number of arguments and abstracts them away with a generic "obj" type:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>
#include <string.h>
#include <stdarg.h>
#include <limits.h>
// basic
// assert expression is true
#define CHECK(expr, ...) do { \
if (!(expr)) { \
fflush(0); \
fprintf(stderr, "ERROR: %s:%d: expression failed: %s", __func__, __LINE__, #expr); \
__VA_OPT__(fprintf(stderr, ": " __VA_ARGS__);) \
fprintf(stderr, "\n"); \
exit(1); \
} \
} while(0)
// fail on allocation error
void *xmalloc(size_t n) {
void *p = malloc(n);
CHECK(p);
return p;
}
// append printf
int rasprintf(char **pnt, char *fmt, ...) {
va_list va;
va_start(va, fmt);
int add = vsnprintf(0, 0, fmt, va);
va_end(va);
CHECK(add > 0);
size_t cur = *pnt ? strlen(*pnt) : 0;
char *new = realloc(*pnt, cur + add);
CHECK(new);
va_start(va, fmt);
int ret = vsnprintf(new + cur, INT_MAX, fmt, va);
va_end(va);
CHECK(ret > 0);
*pnt = new;
return ret;
}
// enumerate types
enum type {
VOID,
INT,
FLOAT,
};
const char *type2str(enum type type) {
switch (type) {
case VOID: return "void";
case INT: return "int";
case FLOAT: return "float";
}
return "unknown";
}
// obj - represents arbitrary value of some type.
typedef struct {
enum type type;
char v[16];
} obj;
// create an argument
obj mk_obj_in(enum type type, void *v, size_t n) {
obj r = {type};
CHECK(sizeof(r.v) > n);
memcpy(r.v, v, n);
return r;
}
obj mk_obj_i(int x) { return mk_obj_in(INT, &x, sizeof(x)); }
obj mk_obj_f(float x) { return mk_obj_in(FLOAT, &x, sizeof(x)); }
#define mk_obj(x) _Generic((x) \
, int: mk_obj_i \
, float: mk_obj_f \
)(x)
// convert obj into actual value
float obj2float(obj i) { CHECK(i.type == FLOAT); float r; memcpy(&r, i.v, sizeof(r)); return r; }
int obj2int(obj i) { CHECK(i.type == INT); int r; memcpy(&r, i.v, sizeof(r)); return r; }
// method - represents function to call.
struct method {
enum type ret;
size_t nargs;
enum type *args;
obj (*func)(int nargs, obj *args);
};
struct methods {
size_t count;
struct method *v;
};
obj dispatch_in(struct methods methods, size_t nargs, obj *args){
for (size_t i = 0; i < methods.count; i++) {
if (methods.v[i].nargs == nargs) {
int same = 1;
for (size_t j = 0; j < nargs; ++j) {
same &= methods.v[i].args[j] == args[j].type;
}
if (same) {
return methods.v[i].func(nargs, args);
}
}
}
// error
char *str = 0;
for (size_t i = 0; i < nargs; i++) {
CHECK(rasprintf(&str, "%s", type2str(args[i].type)));
if (i + 1 < nargs) {
CHECK(rasprintf(&str, ", "));
}
}
CHECK(0, "MethodError: Could not dispatch function with (%s)", str);
free(str);
}
#define dispatch_1(m, a) dispatch_in(m, 1, (obj[]){mk_obj(a)})
#define dispatch_2(m, a, b) dispatch_in(m, 2, (obj[]){mk_obj(a), mk_obj(b)})
#define disaptch_N(_2,_1,N,...) dispatch##N
#define dispatch(m, ...) disaptch_N(__VA_ARGS__,_2,_1)(m, __VA_ARGS__)
// usage
obj f_int(int n, obj *a) { return mk_obj(printf("int x = %d\n", obj2int(a[0]))); }
obj f_float(int n, obj *a) { return mk_obj(printf("float x = %f\n", obj2float(a[0]))); }
obj f_int_float(int n, obj *a) { return mk_obj(printf("int x = %d\n", obj2int(a[0]), obj2float(a[1]))); }
struct methods f = {
3,
(struct method[]){
{ VOID, 1, (enum type[]){ INT }, f_int },
{ VOID, 1, (enum type[]){ FLOAT }, f_float },
{ VOID, 2, (enum type[]){ INT, FLOAT }, f_int_float },
},
};
int main() {
dispatch(f, 5); // prints "x::Int = 5"
dispatch(f, 6.5f); // prints "x::Float = 6.5"
dispatch(f, 7, 8.5f); // prints "x::Int = 7 ; y::Float = 8.5"
dispatch(f, 9.5f, 10); // # throws a MethodError
}
is there a relatively simple way to implement in C a similar dispatch for arbitrary number of arguments?
I would say no.