C · lesson 17 of 25
Compound Literals and Generic Selection in C
Create anonymous structs and arrays with compound literals, and dispatch on a value's type at compile time with C11's _Generic.
By SkillByExample · Published
Compound Literals
A compound literal creates an unnamed object of a given type on the spot — useful when you need a temporary struct or array for exactly one expression and don’t want to declare (and name) a throwaway variable first.
#include <stdio.h>
struct Point { int x, y; };
int manhattan_distance(struct Point p) {
return (p.x < 0 ? -p.x : p.x) + (p.y < 0 ? -p.y : p.y);
}
int sum_array(int *arr, int n) {
int total = 0;
for (int i = 0; i < n; i++) total += arr[i];
return total;
}
int main(void) {
/* Without a compound literal, this needs a named variable first:
struct Point p = {3, -4}; manhattan_distance(p); */
printf("distance = %d\n", manhattan_distance((struct Point){.x = 3, .y = -4}));
/* Same idea for arrays - no named variable needed for a one-off value */
printf("sum = %d\n", sum_array((int[]){10, 20, 30, 40, 50}, 5));
return 0;
}
$ gcc -std=c11 -Wall -Wextra -o compound1 compound1.c
$ ./compound1
distance = 7
sum = 150
The syntax is (type){initializer-list} — a cast-like type name followed by a brace initializer. (struct Point){.x = 3, .y = -4} is a full, real struct Point object; it just doesn’t have a name. The compiler allocates storage for it exactly as it would for a named local variable.
Resetting a Variable in Place
Compound literals also work on the left-hand-adjacent side of an assignment, letting you reset an existing variable without repeating field names:
#include <stdio.h>
struct Point { int x, y; };
int main(void) {
struct Point p = {5, 9};
printf("before reset: %d,%d\n", p.x, p.y);
p = (struct Point){0, 0};
printf("after reset: %d,%d\n", p.x, p.y);
p = (struct Point){.x = 1, .y = 1};
printf("after set: %d,%d\n", p.x, p.y);
return 0;
}
$ gcc -std=c11 -Wall -Wextra -o compound2 compound2.c
$ ./compound2
before reset: 5,9
after reset: 0,0
after set: 1,1
p = (struct Point){0, 0}; builds a fresh temporary struct Point and copies it into p — equivalent to assigning member-by-member, but without naming a second variable to do it.
Generic Selection with _Generic
_Generic picks one of several expressions based on the type of a “controlling expression” — evaluated entirely at compile time, like an overload resolved by hand. It’s the mechanism behind type-generic macros such as <tgmath.h>’s sqrt (which dispatches to sqrtf, sqrt, or sqrtl depending on the argument’s type).
#include <stdio.h>
#define type_name(x) _Generic((x), \
int: "int", \
double: "double", \
char *: "char*", \
default: "unknown")
int main(void) {
int i = 5;
double d = 3.14;
char *s = "hello";
printf("%s\n", type_name(i));
printf("%s\n", type_name(d));
printf("%s\n", type_name(s));
printf("%s\n", type_name(1L)); /* long has no matching association */
return 0;
}
$ gcc -std=c11 -Wall -Wextra -o generic1 generic1.c
generic1.c: In function 'main':
generic1.c:12:11: warning: variable 's' set but not used [-Wunused-but-set-variable]
char *s = "hello";
^
generic1.c:11:12: warning: variable 'd' set but not used [-Wunused-but-set-variable]
double d = 3.14;
^
generic1.c:10:9: warning: variable 'i' set but not used [-Wunused-but-set-variable]
int i = 5;
^
$ ./generic1
int
double
char*
unknown
The output is correct, but the compiler warns that i, d, and s are “set but not used” — even though they’re each passed to type_name. That’s not a false alarm.
The Controlling Expression Is Never Evaluated
_Generic’s controlling expression works like sizeof’s operand: only its type is inspected, the expression itself never actually runs. Proving it takes a controlling expression with an observable side effect:
#include <stdio.h>
#define type_name(x) _Generic((x), \
int: "int", \
double: "double", \
default: "unknown")
int noisy_int(void) {
printf("noisy_int() was called!\n");
return 42;
}
int main(void) {
printf("about to call type_name(noisy_int())\n");
printf("result: %s\n", type_name(noisy_int()));
printf("done\n");
return 0;
}
$ gcc -std=c11 -Wall -Wextra -o generic2 generic2.c
$ ./generic2
about to call type_name(noisy_int())
result: int
done
"noisy_int() was called!" never prints. If _Generic actually evaluated its controlling expression, that line would appear between “about to call” and “result: int” — it doesn’t, because the compiler only needs noisy_int()’s return type (int) to pick a branch, and discards the call itself. This is exactly why the earlier example’s i, d, and s triggered “set but not used”: their values were never read, only their types.
Practice
- Add a
longassociation totype_namesotype_name(5L)reports"long"instead of falling through to"unknown".
Solution
#include <stdio.h>
#define type_name(x) _Generic((x), \
int: "int", \
long: "long", \
double: "double", \
default: "unknown")
int main(void) {
printf("%s\n", type_name(5));
printf("%s\n", type_name(5L));
printf("%s\n", type_name(5.0));
return 0;
}
$ ./gpractice1
int
long
double
Each type needs its own type: "label" association — _Generic matches on the controlling expression’s exact type, not a compatible or convertible one.
- Write a
PRINT(x)macro that uses_Genericto pick the rightprintfformat specifier forint,double, andchar *, then actually printsx.
Solution
#include <stdio.h>
#define PRINT(x) printf(_Generic((x), \
int: "%d\n", \
double: "%f\n", \
char *: "%s\n"), (x))
int main(void) {
PRINT(42);
PRINT(3.5);
PRINT("hi");
return 0;
}
$ ./gpractice2
42
3.500000
hi
Here x genuinely is evaluated — _Generic only picks the format string; x itself is passed to printf as a normal, fully-evaluated argument on the outside. This is the pattern real type-generic macros use to sidestep the “controlling expression not evaluated” trap.
- Use a compound literal to call
sum_arraywith three different numbers than the example (no named array variable).
Solution
#include <stdio.h>
int sum_array(int *arr, int n) {
int total = 0;
for (int i = 0; i < n; i++) total += arr[i];
return total;
}
int main(void) {
printf("%d\n", sum_array((int[]){7, 14, 21}, 3));
return 0;
}
$ ./cpractice3
42
- Write an
ABS(x)macro that dispatches toabs()forintandfabs()fordouble.
Solution
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define ABS(x) _Generic((x), \
int: abs, \
double: fabs)(x)
int main(void) {
printf("%d\n", ABS(-7));
printf("%.1f\n", ABS(-2.5));
return 0;
}
$ gcc -std=c11 -Wall -Wextra -o gpractice4 gpractice4.c -lm
$ ./gpractice4
7
2.5
_Generic here selects a function, not a string — abs or fabs — and the trailing (x) outside the _Generic calls whichever one was picked. This is the same shape <tgmath.h> uses internally to give sqrt(x) the right precision for whatever type x is.