While learning about how macros work, I tried defining a macro that works with different datatypes without having to define it multiple types. I used _Generic
to make this task easier. However, after it didn't work, even after making changes multiple times, I asked deepseek to figure it out, which eventually came up with this:
#define FOO(var) _Generic((var), \
int: printf(#var " = %d\n", (int)(var)), \
double: printf(#var " = %lf\n", (double)(var)), \
long: printf(#var " = %ld\n", (long)(var)), \
char: printf(#var " = '%c'\n", (char)(var)), \
float: printf(#var " = %f\n", (float)(var)), \
default:printf(#var " = unknown type\n"))
int main(void) {
int a = 3;
FOO(a); // correctly prints "a = 3"
}
In other code examples in the cpp-reference and on SO I've never seen anyone having to typecast to make this work.
Removing the typecasts (... int: printf(#var " = %d\n",(var)), \
...) shows a warning "Format specifies type 'long' but argument has type 'int'" and gives compile warning (-Wall
enabled):
ue_05.c: In function ‘main’:
ue_05.c:47:14: warning: format ‘%lf’ expects argument of type ‘double’, but argument 2 has type ‘int’ [-Wformat=]
47 | FOO(a);
| ^
ue_05.c:27:25: note: in definition of macro ‘FOO’
27 | double: printf(#var " = %lf\n", (var)), \
| ^~~
ue_05.c:47:14: warning: format ‘%ld’ expects argument of type ‘long int’, but argument 2 has type ‘int’ [-Wformat=]
47 | FOO(a);
| ^
ue_05.c:28:25: note: in definition of macro ‘FOO’
28 | long: printf(#var " = %ld\n", (var)), \
| ^~~
ue_05.c:47:14: warning: format ‘%f’ expects argument of type ‘double’, but argument 2 has type ‘int’ [-Wformat=]
47 | FOO(a);
| ^
ue_05.c:30:25: note: in definition of macro ‘FOO’
30 | float: printf(#var " = %f\n", (var)), \
I have not been able to find out why the typecast is required here and deepseek also was not able to clear things up.
Why is the typecast necessary here and where is it documented?
the #define
simply edits the code nothing more.
when you write
#define FOO(var) _Generic((var), \
int: printf(#var " = %d\n", (var)), \
double: printf(#var " = %lf\n",(var)), \
long: printf(#var " = %ld\n",(var)), \
char: printf(#var " = '%c'\n",(var)), \
float: printf(#var " = %f\n", (var)), \
default:printf(#var " = unknown type\n"))
int main(void) {
int a = 3;
FOO(a);
}
After the preprocessing is done it becomes behind the scenes
int main(void) {
int a = 3;
_Generic((a),
int: printf("a" " = %d\n", (a)),
double: printf("a" " = %lf\n", (a)),
long: printf("a" " = %ld\n", (a)),
char: printf("a" " = '%c'\n",(a)),
float: printf("a" " = %f\n",(a)),
default:printf("a" " = unknown type\n"));
}
When a
is an int
the compiler still sees
printf("a" " = %lf\n", (a))
and the other statements as well. thus, gives you the warning complaining about the variable a
being int
while printf
expecting float
even though that statement will not be executed.
typecasting just makes the compiler consider a
as float
(in the float:
part of _Generic
and so on in the rest of it) thus not complaining about it.
by the way you probably have the -Wall
on.
Edit:
To get rid of the warning do this instead
#include <stdio.h>
#define FOO(var) printf( _Generic((var),\
int: #var " = %d\n",\
double: #var " = %lf\n",\
long: #var " = %ld\n",\
char: #var " = '%c'\n",\
float: #var " = %f\n",\
default: #var " = unknown type\n")\
, var )
int main() {
float a = 2.4f;
FOO(a);
}