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

David Brown via Gcc-help <[email protected]> Tue, 9 Dec 2025 15:57:43 +0100
Newsgroups gmane.comp.gcc.help
Message-ID <[email protected]>
This sounds like a use-case for _Generic :-)

<https://godbolt.org/z/d4jzT9o9x>

The wrapping in the email is going to be a bit odd, but I am sure you 
will get the idea.  And the godbolt link has the code too.

Basically, this is using a nested _Generic macro - first generic on the 
type of "arg", then on the type of "func".  The default in each case 
will be to call a function that is marked with __attribute__((error)). 
Cases where the types match appropriately are then passed on to the real 
generic "real_task_create" function after required cases to void*.

It's a lot more scalable than your solution (new types only need a 
single line added), and more portable - it's C11, and if you are using a 
compiler other than gcc or clang you can skip the __attribute__((error)) 
marker and you'll at least get link-time failures.


It is quite likely that you can take inspiration from this code and make 
something even nicer out of it!



struct task;

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

__attribute__((error("Unsupported task arg type")))
struct task * unsupported_task_arg_type(void);

__attribute__((error("Mixed task and arg type")))
struct task * mixed_task_arg_type(void);

#define _task_create_type(_type, _name, _func, _arg, _stacksize, 
_priority) \
     _type : _Generic((_func), \
         void (*)(_type) : real_task_create((_name), (void (*)(void *)) 
(_func), (void*)(_arg), (_stacksize), (_priority)), \
         default : mixed_task_arg_type() \
     )


#define task_create(_name, _func, _arg, _stacksize, _priority) \
     _Generic((_arg), \
         _task_create_type(int *, _name, _func, _arg, _stacksize, 
_priority), \
         _task_create_type(long long int *, _name, _func, _arg, 
_stacksize, _priority), \
         default : unsupported_task_arg_type() \
     )



void do_good1(int * p);
struct task * make_good1(void) {
     static int x;
     return task_create("Good1", do_good1, &x, 1000, 1);
}

void do_good2(long long int * p);
struct task * make_good2(void) {
     static long long int x;
     return task_create("Good2", do_good2, &x, 1000, 1);
}


#if 0
void do_bad(int * p);
struct task * make_bad(void) {
     static long long int x;
     return task_create("Bad", do_bad, &x, 1000, 1);
}
#endif

#if 0
void do_unsupported(double * p);
struct task * make_unsupported(void) {
     static double x;
     return task_create("Unsupported", do_unsupported, &x, 1000, 1);
}
#endif




On 09/12/2025 13:08, Matthias Pfaller wrote:
> 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
>>>
>>
>