3

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})));
carl.hiass
  • 1,526
  • 1
  • 6
  • 26

3 Answers3

3

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?

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • thanks that's a nice explanation. Could you explain how `...` would be another way to solve the problem? – carl.hiass Mar 02 '21 at 03:40
  • @carl.hiass: If the macro takes just a variadic `...` argument, `__VA_ARGS__` will expand to the entire set of arguments separated by commas, so it will "reassemble" a compound literal that was split into multiple arguments. I forget if using `...` with no non-variadic arguments is technically valid C, but all compilers I'm aware of support it, and I think it is. – R.. GitHub STOP HELPING ICE Mar 02 '21 at 04:02
1

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

M.M
  • 138,810
  • 21
  • 208
  • 365
  • 1
    To clarify, OP's macro invocation has 2 arguments: `(struct Person){"Tom"` and `10}` . – R.. GitHub STOP HELPING ICE Mar 02 '21 at 03:32
  • @M.M. Could you please explain how `a comma inside a matched pair of parentheses is not a separator.` works? Does that mean a parentheses in addition to the function call itself? So...`function((a,b))` instead of `function(a,b)` ? – carl.hiass Mar 02 '21 at 03:41
  • @carl.hiass Yes I give that example in my last paragraph. Also these are not function calls, they are macro replacements. `MACRONAME( argument-list )` is the syntax for macro replacement invocation. Those parentheses are not part of the argument list – M.M Mar 02 '21 at 04:09
0
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.

Antonin GAVREL
  • 9,682
  • 8
  • 54
  • 81