ensure function pointer / argument compatibility for apply-like functions

Matthias Pfaller <[email protected]> Tue, 9 Dec 2025 11:32:13 +0100
Newsgroups gmane.comp.gcc.help
Organization marco Systemanalyse und Entwicklung GmbH
Message-ID <[email protected]>
and now for something completely different...

In our RTOS I have a function called task_create. Originally I declared it as follows:

struct task *task_create(const char *name, void (*f)(void *), void *farg, unsigned 
int stacksize, unsigned int priority);

When the task is created, it is started by calling f(farg). But actually I wanted to 
be able to declare the task function taking an arbitrary typed argument and I wanted 
to make sure that the "farg" argument (which also should be of an arbitrary type) 
passed to task_create matches the prototype of f.

I came up with the following solution. I modified the original prototype as follows:

union __attribute__((__transparent_union__)) __task_arg {
     void *__parg;
     const void *__cparg;
     volatile void *__vparg;
     const volatile void *__cvparg;
     int __iarg;
     unsigned int __uiarg;
     short __siarg;
     unsigned short __usiarg;
};

struct task *_task_create(const char *name, void *f, union __task_arg arg, unsigned 
int stacksize, unsigned int priority);

The task_create function is then replaced by the following macro:

static void __attribute__((error("incompatible arguments"), __unused__, 
__noinline__)) __task_create_error(void) { while (1); }

/*
  * We need to cast arg to unsigned int in the first if-clause
  * because otherwise gcc will emit a
  * warning when arg is something like &variable (object addresses never
  * can be NULL).
  */
#define task_create(name, func, arg, stacksize, priority) ({ \
         if (__builtin_constant_p(arg) && ((unsigned int)(arg)) == 0) { \
             /* null pointer arg is allowed */ \
         } else if ((__builtin_types_compatible_p(__typeof__(&func), void (*)(void 
*)) || \
__builtin_types_compatible_p(__typeof__(&func), void (*)(volatile void *))) && \
                 !__builtin_types_compatible_p(__typeof__(arg), signed char) && \
                 !__builtin_types_compatible_p(__typeof__(arg), char) && \
                 !__builtin_types_compatible_p(__typeof__(arg), short) && \
                 !__builtin_types_compatible_p(__typeof__(arg), int) && \
                 !__builtin_types_compatible_p(__typeof__(arg), long) && \
                 !__builtin_types_compatible_p(__typeof__(arg), unsigned char) && \
                 !__builtin_types_compatible_p(__typeof__(arg), unsigned short) && \
                 !__builtin_types_compatible_p(__typeof__(arg), unsigned int) && \
                 !__builtin_types_compatible_p(__typeof__(arg), unsigned long)) { \
             /* every pointer is accepted when the function expects void * */ \
         } else if ((__builtin_types_compatible_p(__typeof__(func), void (*)(signed 
char)) || \
                 __builtin_types_compatible_p(__typeof__(func), void (*)(char)) || \
                 __builtin_types_compatible_p(__typeof__(func), void (*)(short)) || \
                 __builtin_types_compatible_p(__typeof__(func), void (*)(int)) || \
                 __builtin_types_compatible_p(__typeof__(func), void (*)(long)) || \
                 __builtin_types_compatible_p(__typeof__(func), void (*)(unsigned 
char)) || \
                 __builtin_types_compatible_p(__typeof__(func), void (*)(unsigned 
int)) || \
                 __builtin_types_compatible_p(__typeof__(func), void (*)(unsigned 
long)) || \
__builtin_types_compatible_p(__typeof__(&func), void (*)(signed char)) || \
__builtin_types_compatible_p(__typeof__(&func), void (*)(char)) || \
__builtin_types_compatible_p(__typeof__(&func), void (*)(short)) || \
__builtin_types_compatible_p(__typeof__(&func), void (*)(int)) || \
__builtin_types_compatible_p(__typeof__(&func), void (*)(long)) || \
__builtin_types_compatible_p(__typeof__(&func), void (*)(unsigned char)) || \
__builtin_types_compatible_p(__typeof__(&func), void (*)(unsigned int)) || \
__builtin_types_compatible_p(__typeof__(&func), void (*)(unsigned long))) && \
                 (__builtin_types_compatible_p(__typeof__(arg), signed char) || \
                 __builtin_types_compatible_p(__typeof__(arg), char) || \
                 __builtin_types_compatible_p(__typeof__(arg), short) || \
                 __builtin_types_compatible_p(__typeof__(arg), int) || \
                 __builtin_types_compatible_p(__typeof__(arg), long) || \
                 __builtin_types_compatible_p(__typeof__(arg), unsigned char) || \
                 __builtin_types_compatible_p(__typeof__(arg), unsigned short) || \
                 __builtin_types_compatible_p(__typeof__(arg), unsigned int) || \
                 __builtin_types_compatible_p(__typeof__(arg), unsigned long))) { \
             /* all integer arguments are considered compatible */ \
         } else if (__builtin_types_compatible_p(__typeof__(func), void 
(*)(__typeof__(arg)))) { \
             /* matching type */ \
         } else if (__builtin_types_compatible_p(__typeof__(&(func)), void 
(*)(__typeof__(arg)))) { \
             /* matching type */ \
         } else { \
             __task_create_error(); \
         } \
         _task_create(name, func, arg, stacksize, priority); \
     })

That  way I can e.g. do

void task1(int wd);
task_create("wd", task1, 20, 1024, 10);

But if I would do:

task_create("wd", task1, "hello", 1024, 10);

I would get a compile time error.Is there a simpler way to achieve my goal (I did 
this about 14 years ago...)?

regards, Matthias