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

Matthias Pfaller <[email protected]> Tue, 9 Dec 2025 13:08:20 +0100
Newsgroups gmane.comp.gcc.help
Organization marco Systemanalyse und Entwicklung GmbH
Message-ID <[email protected]>
On 2025-12-09 13:02, Andrew C Aitchison wrote:
>
> I am assuming C rather than C++, but what standard level we can require ?

At the moment I'm using -std=gnu23.

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

-- 
Matthias Pfaller                              Software Entwicklung
marco Systemanalyse und Entwicklung GmbH      Tel   +49 8131 5161 41
Hans-Böckler-Str. 2, D 85221 Dachau           Fax   +49 8131 5161 66
https://www.marco.de                          Email [email protected]
Geschäftsführer Martin Reuter, Torsten Lukas  HRB 171775 Amtsgericht München