Skip to main content
C · lesson 17 of 25
C intermediate 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

  1. Add a long association to type_name so type_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.

  1. Write a PRINT(x) macro that uses _Generic to pick the right printf format specifier for int, double, and char *, then actually prints x.
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.

  1. Use a compound literal to call sum_array with 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
  1. Write an ABS(x) macro that dispatches to abs() for int and fabs() for double.
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.

Frequently Asked Questions

What C standard introduced compound literals and _Generic?
Compound literals (the (type){initializer} syntax) arrived in C99. Generic selection (_Generic) arrived in C11, twelve years later — it was added specifically to let library authors write type-generic macros like <tgmath.h>'s math functions without relying on GCC-specific extensions.
Is _Generic's controlling expression evaluated at runtime?
No. Only its type is used to pick a branch, the same way sizeof's operand is not evaluated. This means a controlling expression with a side effect (a function call, an increment) silently never runs — GCC will even warn 'variable set but not used' for a variable that is only ever passed as a _Generic controlling expression, which is a strong hint you've misunderstood how it works.
Do compound literals have a name I can take the address of later?
No. A compound literal is an unnamed object with automatic storage duration matching the block it appears in (unless declared outside any function, where it has static storage). Its address is valid only until execution leaves that block — returning a pointer to a compound literal from a function, or storing one past its enclosing block, produces a dangling pointer.
Can I use _Generic with types other than the ones I list?
Yes — the default: association catches every type not explicitly listed, the same role default plays in a switch statement. Without a default and no matching type, the compiler rejects the expression at compile time rather than falling through to undefined behavior.