Passing compound literal to _Generic not working

Viewed 134

In the following code:

struct Person {
    char* name;
    int age;
};

struct Book {
    char* title;
    char* author;
};
#define MYTYPE(X)   _Generic((X), int: "int", float: "float", double: "double", struct Book: "book", struct Person: "person", default: "other")

The following works:

struct Book ulysses = {"ulysses", "james"};
printf("%s\n", MYTYPE(ulysses));
struct Person jim;
jim = (struct Person) {"Tom", 20};
printf("%s\n", MYTYPE(jim));

However, if I try passing a compound literal it fails:

printf("%s\n", MYTYPE((struct Person){"Tom", 10}));

gen.c:25:53: error: macro "MYTYPE" passed 2 arguments, but takes just 1
printf("%s\n", MYTYPE((struct Person){"Tom", 10}));
............................................................................... ^

What seems to be the issue with the passing of the struct Person to the MYTYPE macro?


Update: it seems double wrapping the expression in parens fixes this but I'm not sure why that's required:

printf("%s\n", MYTYPE(((struct Person){"Tom", 10})));
3 Answers

You have invoked MYTYPE with 2 arguments: (struct Person){"Tom" and 10}. Unlike parentheses, braces are not syntactically meaningful at the preprocessor level and do not suppress the role of the comma as a macro argument separator. You need to parenthesize compound literals to avoid this. Alternatively, in some situations (including yours) you can use ... and __VA_ARGS__ to make a variadic macro that avoids the problem. See also my question: Compound literals and function-like macros: bug in gcc or the C standard?

The issue you're having is due to the preprocessor syntax . MACRONAME(a,b) means to invoke MACRONAME with the two arguments a and b , regardless of whether the combination of a , the comma, and b happened to form some semantically valid expression. (At the preprocessing stage we have not got so far as arranging the tokens into expressions).

The exceptions to this relate to string literals, and matched pairs of parenthesis:

  • a comma in a string literal is not an argument separator
  • a comma inside a matched pair of parentheses is not a separator. (This refers to parentheses within the argument list, not in the macro replacement syntax).

Hence why the double-parentheses work: MACRONAME((a,b)) means to invoke MACRONAME with a single argument of (a,b) .

typedef struct Person {
    char* name;
    int age;
}      t_Person; // (sorry I took this habit)

Note that if you declare it you don't need to double wrap it anymore:

t_Person a = {"Tom", 10};
printf("%s\n", MYTYPE((((t_Person){"Tom", 10})))); -> works
printf("%s\n", MYTYPE(a)); -> works, saves 4 parenthesis.

My guess is that MYTYPE doesn't know what is inside itself, because of _Generic it could be int, float etc... so the compiler thinks that your struct between bracket is actually a cast and not a declaration, adding another parenthesis resolves the conflict.

I can prove it with:

#define MYTYPE(X)   _Generic((X),  char: "char", int: "int", float: "float", double: "double", struct Book: "book", struct Person: "person", default: "other")

printf("%s\n", MYTYPE((int){'a'})); // -> output int
printf("%s\n", MYTYPE((char){'a'})); // -> output char

First, the type of controlling-expression undergoes lvalue conversions. The conversion is performed in type domain only: it discards the top-level cvr-qualifiers and atomicity and applies array-to-pointer/function-to-pointer transformations to the type of the controlling expression, without initiating any side-effects or calculating any values.

Related