I am delighted by C11's _Generic mechanism - switching on type is something I miss from C++. It is however proving difficult to compose.
For an example, given functions:
bool write_int(int);
bool write_foo(foo);
bool write_bar(bar);
// bool write_unknown is not implemented
I can then write
#define write(X) _Generic((X), \
int : write_int, \
foo: write_foo, \
bar: write_bar, \
default: write_unknown)(X)
and, provided I don't try to use &write or pass it to a function, I can call write(obj) and, provided obj is an instance of one of those types, all is well.
However, in general foo and bar are entirely unrelated to each other. They are defined in different headers, rarely (but occasionally) used together in a single source file. Where then should the macro expanding to the _Generic be written?
At present, I am accumulating header files called things like write.h, equal.h, copy.h, move.h each of which contains a set of function prototypes and a single _Generic. This is workable, but not brilliant. I don't like the requirement to collect together a list of every type in the program in a single place.
I would like to be able to define type foo in a header file, along with the function write_foo, and somehow have the client code able to call the 'function' write. Default looks like a vector through which this could be achieved.
The closest match I can find on this site is c11 generic adding types which has a partial solution, but it's not quite enough for me to see how to combine the various macros.
Let's say that, somewhere in a header file that defines write_bar, we have an existing macro definition:
#define write(x) _Generic((x), bar: write_bar, default: some_magic_here)(x)
Or we could omit the trailing (x)
#define write_impl(x) _Generic((x), bar: write_bar, default: some_magic_here)
Further down in this header, I would like a version of write() that handles either foo or bar. I think it needs to call the existing macro in its default case, but I don't believe the preprocessor is able to rename the existing write macro. If it were able to, the following could work:
#ifndef WRITE_3
#define WRITE_3(X) write(x)
#undef write(x)
#define write(x) __Generic((x),foo: write_foo,default: WRITE_3)(x)
Having just typed that out I can sort-of see a path forward:
// In bar.h
#ifndef WRITE_1
#define WRITE_1(x) __Generic((x), bar: write_bar)
#elif !defined(WRITE_2)
#define WRITE_2(x) __Generic((x), bar: write_bar)
#elif !defined(WRITE_3)
#define WRITE_3(x) __Generic((x), bar: write_bar)
#endif
// In foo.h
#ifndef WRITE_1
#define WRITE_1(x) __Generic((x), foo: write_foo)
#elif !defined(WRITE_2)
#define WRITE_2(x) __Generic((x), foo: write_foo)
#elif !defined(WRITE_3)
#define WRITE_3(x) __Generic((x), foo: write_foo)
#endif
// In write.h, which unfortunately needs to be included after the other two
// but happily they can be included in either order
#ifdef WRITE_2
#define write(x) WRITE_1(x) WRITE_2(x) (x)
#elif
// etc
#endif
This doesn't actually work though, since I can't find a way to make WRITE_N(x) expand to nothing when x doesn't match the argument list. I see the error
controlling expression type 'struct foo' not compatible with any generic association type
Or
expected expression // attempting to present an empty default clause
I believe to distribute the write() definition between several files | macros I need to work around either of the above. A _Generic clause which reduces to nothing in the default case would work, as would one which reduces to nothing if none of the types match.
Getting yet more hackish, if the functions take a pointer to a struct instead of an instance of one, and I provide write_void(void*x) {(void)x;} as the default option, then the code does compile and run. However, expanding write as
write(x) => write_void(x); write_foo(x); write_void(x);
is clearly pretty bad in itself, plus I don't really want to pass everything by pointer.
So - can anyone see a way to define a single _Generic 'function' incrementally, i.e. without starting with a list of all types it will map over? Thank you.