Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you use _Generic with structs that are typedef-ed in C?

I'm new to C and currently exploring the concept of function overloading via the use of _Generic in C11.

In this SO post, @Lundin provides the following code snippet, which I've gotten running with no issues:

#include <stdio.h>

void func_int (int x) { printf("%d\n", x); }
void func_char (char ch) { printf("%c\n", ch); }

#define func(param)          \
  _Generic((param),          \
    int:  func_int(param),   \
    char: func_char(param)); \

int main() 
{
  func(1);
  func((char){'A'});
}

@Lundin also mentioned the following:

A much better way would be to make a function interface with a single struct parameter, which can be adapted to contain all the necessary parameters. With such an interface you can use the above simple method based on _Generic. Type safe and maintainable.

So I decided to extend the above example to structs to see it in action.

Below is my attempt:

#include <stdlib.h>
#include <stdio.h>

typedef struct args_int Args_int;
typedef struct args_char Args_char;

void func_int(Args_int args);
void func_char(Args_char args);

struct args_int {
    int val;
};
struct args_char {
    char val;
};

#define func(param)\
    _Generic((param),\
        Args_int: func_int(param),\
        Args_char: func_char(param));


void func_int (Args_int args) {
    printf("%d\n", args.val);
}

void func_char (Args_char args) {
    printf("%c\n", args.val);
}

int main(void) {
    Args_char args = {0};
    args.val = 'A';
    func(args);
}

However, unfortunately, I get the following compilation error, which complains that I've passed in an Args_char when the compiler is expecting an Args_int. Clearly, my intent is to pass an Args_char and my expectation is for func_char to be called as a result.

struct_args_by_val_executable.c:35:10: error: passing 'Args_char' (aka 'struct args_char') to parameter of incompatible type 'Args_int' (aka 'struct args_int')
    func(args);
         ^~~~
struct_args_by_val_executable.c:20:28: note: expanded from macro 'func'
        Args_int: func_int(param),\
                           ^~~~~
struct_args_by_val_executable.c:24:25: note: passing argument to parameter 'args' here
void func_int (Args_int args) {
                        ^
1 error generated.

Why isn't my example working as expected and what is the fix here?

On a related note, I managed to get a "pointer to a struct" version working without any issues as shown below. However, given the above compilation error, I feel as though this may have been a fluke?

#include <stdio.h>

typedef struct args_int Args_int;
typedef struct args_char Args_char;

void func_int(Args_int *args);
void func_char(Args_char *args);

struct args_int {
    int val;
};
struct args_char {
    char val;
};

#define func(param)\
    _Generic((param),\
        Args_int*: func_int(param),\
        Args_char*: func_char(param));


void func_int (Args_int *args) {
    printf("%d\n", args->val);
}

void func_char (Args_char *args) {
    printf("%c\n", args->val);
}

int main(void) {
    Args_char args = {0};
    args.val = 'A';
    func(&args);
}

The output to the above is as expected:

A
like image 944
p4t Avatar asked Sep 27 '21 13:09

p4t


People also ask

How does typedef struct work in C?

The C language contains the typedef keyword to allow users to provide alternative names for the primitive (e.g.,​ int) and user-defined​ (e.g struct) data types. Remember, this keyword adds a new name for some existing data type but does not create a new type.

What is the difference between struct and typedef struct in C?

Basically struct is used to define a structure. But when we want to use it we have to use the struct keyword in C. If we use the typedef keyword, then a new name, we can use the struct by that name, without writing the struct keyword.

What is the purpose of typedef how typedef is useful in structure?

typedef is a predefined keyword. You can replace the name of the existing data type with the name which you have provided. This keyword helps in creating a user-defined name for an existing data type. You can also use the typedef keyword with structures, pointers, arrays etc.

Why do we use typedef in C?

typedef is a reserved keyword in the programming languages C and C++. It is used to create an additional name (alias) for another data type, but does not create a new type, except in the obscure case of a qualified typedef of an array type where the typedef qualifiers are transferred to the array element type.

When to use typedef names for structs in C++?

Such a declaration is most convenient if you learned C++ first, where you may omit the struct keyword if the name is not ambiguous. typedef names for structs could be in conflict with other identifiers of other parts of the program.

Why do we use ‘typedef’ in C++?

Using ‘typedef’, we cannot create a new datatype but define a new name for already existing type. This statement tells the compiler to recognize ‘bhanu’ as another name for ‘int’. ‘bhanu’ is used to create another variable ‘a’ . ‘bhanu a ‘declares ‘a’ as a variable of type ‘int’.

When to use a new defined type in C?

This is very used for example when working with linked lists in C The new defined type can be used just as other basic types for almost everything. Try for example to create an array of type student and see how it works. Structs can be copied or assigned but you can not compare them!

What is the typedef for point in C++?

typedef struct Point Point; struct Point { int x, y; }; to have advantage of both possible definitions of point. Such a declaration is most convenient if you learned C++ first, where you may omit the struct keyword if the name is not ambiguous. typedef names for structs could be in conflict with other identifiers of other parts of the program.


2 Answers

The problem is that all expressions used in _Generic must be valid. In your example macro func expands to:

int main(void) {
    Args_char args = {0};
    args.val = 'A';
    _Generic(args,
             Args_int: func_int(args),
             Args_char: func_char(args));
}

Note that using func_int(args) for args which is Args_char is causing an error.

The solution to that is using _Generic to select a function pointer, next apply arguments to it.

#define func(param)                       \
    _Generic((param),                     \
             Args_int: func_int,          \
             Args_char: func_char) (param)
like image 164
tstanisl Avatar answered Nov 15 '22 05:11

tstanisl


Just posting this "for the record":

That's actually not a very good code example of me that you've found there! It's much better to include the parameter list outside the _Generic clause just as @tstanisl just mentioned in their answer (I'd accept that one as the answer to your question). That's how the C committee intended the feature to be used even, you can find such examples in the standard itself, see for example 6.5.1:

#define cbrt(X) _Generic((X),               \
                        long double: cbrtl, \
                        default: cbrt,      \
                        float: cbrtf        \
                        )(X)

The code snippet you found works by luck since char and int can be converted to each other when calling a function "as if by assignment". When using two non-compatible types that cannot get implicitly converted, the macro would expand incorrectly for the wrong type.

I've now updated https://stackoverflow.com/a/44633838/584518 to use this:

#define func(param)          \
  _Generic((param),          \
    int:  func_int,          \
    char: func_char)(param)  \
like image 32
Lundin Avatar answered Nov 15 '22 03:11

Lundin