Re: ensure function pointer / argument compatibility for apply-like functions

Andrew C Aitchison via Gcc-help <[email protected]> Tue, 9 Dec 2025 12:02:52 +0000 (GMT)
Newsgroups gmane.comp.gcc.help
Message-ID <[email protected]>
I am assuming C rather than C++, but what standard level we can require ?

On Tue, 9 Dec 2025, Matthias Pfaller wrote:

> 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
>

-- 
Andrew C. Aitchison                      Kendal, UK
                    [email protected]