Generic function pointer + Clang block dispatch

2026/07/28

#c, #experiment

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.