Search code examples
cmacrostypeof

type checking in C macro


I'm trying to do a type check at compile time in one of my macros. The simplified code looks something like this:

I have a structure consisting of:

  • an int representing a type of a function interface and
  • a generic function pointer
typedef void(*generic_func_t)(void);

typedef struct
{
     int            type;
     generic_func_t function;
} func_def_t;

to initialize this structure I have defined two macros;

#define INIT_FUNC1_TYPE(function) {1, (generic_func_t)function}
#define INIT_FUNC2_TYPE(function) {2, (generic_func_t)function}

The usage would be something like this:

int(*func1_t)(int, int, int);
int(*func2_t)(int, int);

int foo(int a, int b)
{
   return a + b;
}

func_def_t active_func = INIT_FUNC2_TYPE(foo);

...

int a = 2;
int b = 3;
int c = 4;
int d;

if (active_func.type == 1)
{
   func1_t func = (func1_t)active_func.function;
   d = func(a, b, c);
}

if (active_func.type == 2)
{
   func2_t func = (func2_t)active_func.function;
   d = func(a, b);
}


My problem now is that any type of function can be cast to (void*).

But it must somehow be possible to design the macro in such a way that only a certain type of function-interface is accepted by the compiler. Maybe with typeof?

Let's say there are the function types:

void(*func1_t)(int, int, int);
int(*func2_t)(float, int);

How can I do that?

Edited: I changed the pointer from void* to generic_func_t but the essence of the questions stays the same. The macro INIT_FUNC1_TYPE should only accept functions with an interface like func1_t. The macro INIT_FUNC2_TYPE should only accept functions with an interface like func2_t.


Solution

  • I have a structure consisting of:

    • an int representing a type of a function interface and
    • a void pointer to the function

    No, you haven't, because a void * points to an object not a function. However, you can convert freely (via typecast) among function pointer types, so you can choose any bona fide function pointer type for your purpose. Perhaps void (*)(void):

    typedef struct {
         int  type;
         void (*function)(void);
    } func_def_t;
    

    You will need to convert back to the correct function pointer type to call the function anyway, so this is no harder to use than void *.

    My problem now is that any type of function can be cast to (void*).

    Well, no type of function pointer can be cast to void * by a strictly conforming program. But if your compiler accepts such conversions as an extension then yes, presumably it works for any function pointer type. And so does converting from any function pointer type to any other function pointer type, including in strictly conforming programs.

    But it must somehow be possible to design the macro in such a way that only a certain type of function-interface is accepted by the compiler. Maybe with typeof?

    Standard C does not have typeof, though I believe it's scheduled for inclusion in C23. But I don't think that helps you.

    However, you can use a type-generic expression in your macro to limit it to certain function pointer types. In fact, that could allow you to have a single macro instead of two.

    Per-type example:

    typedef void (func)(void);
    typedef struct {
         int  type;
         func *function;
    } func_def_t;
    
    #define INIT_FUNC1_TYPE(f) {1, _Generic((f), void (*)(int, int, int) : (func *) f) }
    #define INIT_FUNC2_TYPE(f) {2, _Generic((f), int (*)(float, int) : (func *) f) }
    

    The compiler will object if one of those macros is invoked with an argument that does not match one of the type names in the _Generic selection within, and each of those affords exactly one option.

    But that misses out on the opportunity to do everything with a single macro. Consider this:

    #define INIT_FUNC_TYPE(f) { \
        .type = _Generic((f), \
            void (*)(int, int, int) : 1, \
            int (*)(float, int)     : 2), \
        .function = (func *) f \
    }
    

    Now the (one) macro chooses the function-type key based on the type of the macro argument (and the compiler rejects arguments that don't match any of the specified types). This scales better than an approach requiring a separate macro for each supported function signature.