Generic function pointers + Clang blocks
Function pointers and Clang blocks ^{ } are distinct types and don’t work together interchangeably. Which is annoying when you like the blocks syntax. But it isn’t standard, so putting it in a public open source library means either making two duplicate functions or two separate entry points.
So I wrote this:
#define UNWRAP(...) __VA_ARGS__
// --- FOR_EACH infrastructure (up to 8 params, can obvious be extended if needed) ---
#define PP_NARGS(...) PP_NARGS_(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define PP_NARGS_(a,b,c,d,e,f,g,h,n,...) n
#define PP_CONCAT(a, b) PP_CONCAT_(a, b)
#define PP_CONCAT_(a, b) a ## b
#define PP_FOREACH(m, ...) PP_CONCAT(PP_FOREACH_, PP_NARGS(__VA_ARGS__))(m, __VA_ARGS__)
#define PP_FOREACH_0(m)
#define PP_FOREACH_1(m, a) m(a)
#define PP_FOREACH_2(m,a,...) m(a), PP_FOREACH_1(m, __VA_ARGS__)
#define PP_FOREACH_3(m,a,...) m(a), PP_FOREACH_2(m, __VA_ARGS__)
#define PP_FOREACH_4(m,a,...) m(a), PP_FOREACH_3(m, __VA_ARGS__)
#define PP_FOREACH_5(m,a,...) m(a), PP_FOREACH_4(m, __VA_ARGS__)
#define PP_FOREACH_6(m,a,...) m(a), PP_FOREACH_5(m, __VA_ARGS__)
#define PP_FOREACH_7(m,a,...) m(a), PP_FOREACH_6(m, __VA_ARGS__)
#define PP_FOREACH_8(m,a,...) m(a), PP_FOREACH_7(m, __VA_ARGS__)
// --- Parameter pair extractors ---
#define PP_TYPE(pair) PP_TYPE_ pair
#define PP_TYPE_(t, n) t
#define PP_NAME(pair) PP_NAME_ pair
#define PP_NAME_(t, n) n
#define PP_DECL(pair) PP_DECL_ pair
#define PP_DECL_(t, n) t n
#define TYPES(...) PP_FOREACH(PP_TYPE, __VA_ARGS__)
#define NAMES(...) PP_FOREACH(PP_NAME, __VA_ARGS__)
#define DECLS(...) PP_FOREACH(PP_DECL, __VA_ARGS__)
/*
* Main implementation macro.
* GENERIC_CALLBACK_IMPL(name, ret, (type1, name1), (type2, name2), ...)
*/
#define GENERIC_CALLBACK_IMPL(name, ret, ...) \
typedef struct { \
ret (^block)(TYPES(__VA_ARGS__), void *user_data); \
void *user_data; \
} name##_block_wrapper_t; \
\
ret name##_impl(DECLS(__VA_ARGS__), void *user_data, ret (*callback)(TYPES(__VA_ARGS__), void *user_data)); \
ret name##_block(DECLS(__VA_ARGS__), void *user_data, \
ret (^callback)(TYPES(__VA_ARGS__), void *user_data)); \
\
static ret name##_block_callback(DECLS(__VA_ARGS__), void *wrapper_data) { \
name##_block_wrapper_t *wrapper = (name##_block_wrapper_t *)wrapper_data; \
return wrapper->block(NAMES(__VA_ARGS__), wrapper->user_data); \
} \
\
ret name##_block(DECLS(__VA_ARGS__), void *user_data, ret (^callback)(TYPES(__VA_ARGS__), void *user_data)) { \
name##_block_wrapper_t wrapper = { \
.block = callback, \
.user_data = user_data \
}; \
return name##_impl(NAMES(__VA_ARGS__), &wrapper, name##_block_callback); \
} \
\
ret name##_impl(DECLS(__VA_ARGS__), void *user_data, \
ret (*callback)(TYPES(__VA_ARGS__), void *user_data))
// Dispatch macro using _Generic
#define GENERIC_CALLBACK(name, ret, types, callback) \
_Generic((callback), \
ret (^)(UNWRAP types, void *): name##_block, \
ret (*)(UNWRAP types, void *): name##_impl)
// Convenience call macro
#define GENERIC_CALL(name, ret, types, callback, ...) \
GENERIC_CALLBACK(name, ret, types, callback)(__VA_ARGS__, callback)
// --- Examples ---
#define directory_walk(path, userdata, callback) \
GENERIC_CALL(directory_walk, int, (const char *), callback, path, userdata)
GENERIC_CALLBACK_IMPL(directory_walk, int,
(const char *, path))
{
if (!path || !callback)
return 0;
printf("Walking directory: %s\n", path);
int result = callback(path, user_data);
return result;
}
#define process_file(path, flags, size, userdata, callback) \
GENERIC_CALL(process_file, bool, (const char *, int, size_t *), callback, path, flags, size, userdata)
GENERIC_CALLBACK_IMPL(process_file, bool, (const char *, path), (int, flags), (size_t *, size)) {
if (!path || !callback)
return false;
printf("Processing file: %s (flags=%d)\n", path, flags);
bool result = callback(path, flags, size, user_data);
if (size) {
printf(" Size set to: %zu\n", *size);
}
return result;
}
int directory_walk_callback(const char *path, void *user_data) {
printf(" [func ptr] callback: %s\n", path);
return 1;
}
bool process_file_callback(const char *path, int flags, size_t *size, void *user_data) {
printf(" [func ptr] callback: %s (flags=%d)\n", path, flags);
if (size)
*size = 12345;
return true;
}
int main(void) {
directory_walk("/test/path1", NULL, directory_walk_callback);
directory_walk("/test/path2", NULL, ^(const char *path, void *user_data) {
printf(" [block] callback: %s\n", path);
return 1;
});
size_t size1 = 0;
process_file("/file1.txt", 42, &size1, NULL, process_file_callback);
size_t size2 = 0;
process_file("/file2.txt", 99, &size2, NULL, ^bool(const char *path, int flags, size_t *size, void *user_data) {
printf(" [block] callback: %s (flags=%d)\n", path, flags);
if (size) *size = 67890;
return true;
});
return 0;
}
Walking directory: /test/path1
[func ptr] callback: /test/path1
Walking directory: /test/path2
[block] callback: /test/path2
Processing file: /file1.txt (flags=42)
[func ptr] callback: /file1.txt (flags=42)
Size set to: 12345
Processing file: /file2.txt (flags=99)
[block] callback: /file2.txt (flags=99)
Size set to: 67890
Write one function body, and the macro automatically creates the wrappers. It uses _Generic compile-time dispatch and both paths funnel back into a single implementation function. Everything else is hidden to keep things clean.
This is all the user needs to do:
GENERIC_CALLBACK_IMPL(directory_walk, int, (const char *, path)) {
return callback(path, user_data);
}
#define directory_walk(path, userdata, callback) \
GENERIC_CALL(directory_walk, int, (const char *), callback, path, userdata)
directory_walk("/test/path1", NULL, directory_walk_callback);
directory_walk("/test/path2", NULL, ^(const char *path, void *ud) {
...
});
Dispatch
#define GENERIC_CALLBACK(name, ret, types, callback) \
_Generic((callback), \
ret (^)(UNWRAP types, void *): name##_block, \
ret (*)(UNWRAP types, void *): name##_impl)
_Generic inspects the type of callback at compile time: If it’s a block pointer (ret (^)(const char *, void *)), select directory_walk_block. If it’s a function pointer (ret (*)(const char *, void *)), select directory_walk_impl. Then GENERIC_CALL calls the selected function with (path, userdata, callback) as arguments.
Result
GENERIC_CALLBACK_IMPL generates all the plumbing. Take this example:
GENERIC_CALLBACK_IMPL(directory_walk, int, (const char *, path))
The final expanded:
// 1. Block wrapper struct — holds the block + its user_data
typedef struct {
int (^block)(const char *, void *user_data);
void *user_data;
} directory_walk_block_wrapper_t;
// 2. Forward declarations
int directory_walk_impl(const char *path, void *user_data, int (*callback)(const char *, void *user_data));
int directory_walk_block(const char *path, void *user_data, int (^callback)(const char *, void *user_data));
// 3. Block trampoline — wraps a block so it looks like a function pointer
static int directory_walk_block_callback(const char *path, void *wrapper_data) {
directory_walk_block_wrapper_t *wrapper = wrapper_data;
return wrapper->block(path, wrapper->user_data);
}
// 4. Block entry point — stores block+user_data in wrapper, calls _impl
int directory_walk_block(const char *path, void *user_data, int (^callback)(const char *, void *user_data)) {
directory_walk_block_wrapper_t wrapper = { .block = callback, .user_data = user_data };
return directory_walk_impl(path, &wrapper, directory_walk_block_callback);
}
// 5. The actual implementation (user writes the body after the macro)
int directory_walk_impl(const char *path, void *user_data, int(*callback)(const char *, void *user_data)) {
// user code goes here...
}
These features require GCC/Clang and C11 (_Generic). But it’s easy to make it portable.
#if __has_extension(blocks) && __has_include(<Block.h>)
// original code here!
#else
// Without blocks: only the _impl function, no dispatch needed
#define GENERIC_CALLBACK_IMPL(name, ret, ...) \
ret name##_impl(DECLS(__VA_ARGS__), void *user_data, ret (*callback)(TYPES(__VA_ARGS__), void *user_data))
#define GENERIC_CALLBACK(name, ret, types, callback) \
name##_impl
#endif
Now the code will simply just use function pointers without any changes. Blocks are entirely optional. Here is a gist for the full file.