C _Generic

Em C, cada função tem um tipo fixo: abs trabalha com int, fabs com double, e o printf precisa que você diga %d ou %f. Não existe "uma função que aceita qualquer coisa". O _Generic C11 resolve parte disso: ele olha o tipo de uma expressão e escolhe, na hora de compilar, qual código usar. Com ele você vai criar uma macro que diz o nome do tipo de qualquer valor e uma imprimir(x) que funciona com número e com texto.

Você vai aprender
  • O que o _Generic faz e como ler a sua "lista de casos": _Generic(x, int: …, double: …).
  • A macro TIPO(x), que mostra o nome do tipo de um valor (e descobre que 'A' é int em C).
  • O caso default, para os tipos que você não listou.
  • Como escolher uma função pelo tipo e criar um imprimir(x) genérico.
  • O tgmath.h, que faz sqrt e fabs se adaptarem ao tipo, e o seu próprio ABS(x).
  • Os erros reais: tipo sem caso, dois casos iguais e o ramo que não foi escolhido mas não compila.

Os programas desta aula foram compilados com o gcc 15.2 na máquina da Ana (usuária ana, computador notebook), na pasta ~/c, com o comando padrão do guia: gcc -std=c23 -Wall -Wextra arquivo.c -o programa. Antes desta aula, vale ter visto Macros com Parâmetros e Ponteiros para Funções.


A ideia: a recepção que olha o documento

Pense na recepção de um prédio. A pessoa chega e entrega um documento. A recepcionista olha que tipo de documento é: se é crachá de funcionário, manda para o elevador A; se é RG de visitante, manda para o balcão de cadastro; se é qualquer outra coisa, manda para a portaria. Ela não lê o nome nem a foto: só o tipo do documento decide o caminho.

O _Generic é essa recepcionista. Você entrega uma expressão e uma lista de "tipo → o que usar". O compilador olha o tipo da expressão e coloca no lugar só o item escolhido. Tudo isso acontece na compilação: quando o programa roda, a escolha já foi feita.

Na recepçãoNo _Generic
O documento entregueA expressão de controle (o primeiro item, antes da primeira vírgula)
"Crachá → elevador A"Uma associação: int: "inteiro"
"Qualquer outro → portaria"O caso default:
A pessoa vai para um lugar sóO _Generic inteiro vira uma expressão: a escolhida

O nome tem um sublinhado e letra maiúscula (_Generic) porque, em 2011, o C precisava de uma palavra nova que não quebrasse programas antigos, e nomes que começam com _ + maiúscula sempre foram reservados para a linguagem. "Generic" quer dizer genérico: que serve para vários tipos.


1. O primeiro _Generic

Este programa pergunta ao compilador se cada variável é inteira ou "com vírgula":

#include <stdio.h>

int main(void) {
    int idade = 29;
    double altura = 1.65;

    const char *a = _Generic(idade, int: "inteiro", double: "com vírgula");
    const char *b = _Generic(altura, int: "inteiro", double: "com vírgula");

    printf("idade é %s\n", a);
    printf("altura é %s\n", b);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra primeiro.c -o primeiro
$ ./primeiro
idade é inteiro
altura é com vírgula
_Generic( … )
Parece uma função, mas é uma palavra-chave da linguagem. Tudo entre os parênteses é resolvido pelo compilador.
idade
A expressão de controle. O compilador só olha o tipo dela (int); o valor 29 não importa.
int: "inteiro"
Uma associação: "se o tipo for int, use "inteiro"". Do lado esquerdo vai um tipo; do lado direito, uma expressão qualquer.
, double: "com vírgula"
Outra associação. A lista pode ter quantas você quiser, separadas por vírgula, em qualquer ordem.
const char *a = …
O _Generic inteiro "vira" a expressão escolhida. Na linha 7, é como se estivesse escrito const char *a = "inteiro";.

Repare que as duas linhas do _Generic são idênticas, menos a variável. O resultado mudou só porque o tipo mudou. É isso que torna o _Generic útil dentro de macros: escreve-se uma vez e usa-se com qualquer variável.

A expressão de controle não é executada

O compilador só precisa do tipo, então a expressão de controle nunca roda. Um i++ ali dentro não soma nada:

#include <stdio.h>

int main(void) {
    int i = 5;
    const char *t = _Generic(i++, int: "int", default: "outro");
    printf("%s, i = %d\n", t, i);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra naoavalia.c -o naoavalia
$ ./naoavalia
int, i = 5                « o i++ não aconteceu

É o mesmo que acontece com o sizeof (aula Tamanho dos Tipos). O gcc ficou calado; o Clang, compilador do editor do guia, avisa: expression with side effects has no effect in an unevaluated context [-Wunevaluated-expression] ("expressão com efeito colateral não tem efeito num contexto que não é avaliado"). Moral: nunca ponha ++, chamadas de função ou atribuições na expressão de controle.


2. A macro TIPO(x)

Sozinho, o _Generic fica comprido. O normal é escondê-lo numa macro (aula Macros com Parâmetros). Esta aqui diz o nome do tipo de qualquer coisa que você passar:

#include <stdio.h>

#define TIPO(x) _Generic((x),   \
    int: "int",                 \
    double: "double",           \
    float: "float",             \
    char: "char",               \
    bool: "bool",               \
    char *: "char *",           \
    default: "outro tipo")

int main(void) {
    int idade = 29;
    char letra = 'A';
    printf("idade   -> %s\n", TIPO(idade));
    printf("1.65    -> %s\n", TIPO(1.65));
    printf("1.65f   -> %s\n", TIPO(1.65f));
    printf("letra   -> %s\n", TIPO(letra));
    printf("'A'     -> %s\n", TIPO('A'));
    printf("true    -> %s\n", TIPO(true));
    printf("\"Ana\"   -> %s\n", TIPO("Ana"));
    printf("10L     -> %s\n", TIPO(10L));
    printf("idade+0.5 -> %s\n", TIPO(idade + 0.5));
    return 0;
}
$ gcc -std=c23 -Wall -Wextra tipo.c -o tipo
$ ./tipo
idade   -> int
1.65    -> double
1.65f   -> float
letra   -> char
'A'     -> int                « surpresa!
true    -> bool
"Ana"   -> char *
10L     -> outro tipo
idade+0.5 -> double
#define TIPO(x)
Uma macro com parâmetro: cada TIPO(algo) é trocado pelo _Generic com algo no lugar de x.
\ no fim da linha
Diz ao pré-processador "a macro continua na próxima linha". Sem ele, a macro acabaria na primeira linha. Não pode haver nada depois da barra, nem um espaço.
(x)
O parâmetro entre parênteses, como sempre em macros: assim TIPO(idade + 0.5) funciona.
bool: "bool"
Em C23, bool é um tipo próprio, diferente de int (aula Booleanos).
char *: "char *"
Um texto entre aspas é um array de char, mas no _Generic ele vira ponteiro para o primeiro caractere (aula Ponteiros e Strings).
default: "outro tipo"
O caso para "nenhum dos de cima". O 10L é long, que não está na lista, e caiu aqui.

O que a TIPO revelou

  • 'A' é int, não char. Em C, um caractere entre aspas simples tem tipo int (vale 65). Só a variável letra, declarada como char, é char. (Em C++ seria char: é uma das diferenças entre as duas linguagens.)
  • 1.65 é double; com o f no fim, 1.65f, é float (aula float e double).
  • idade + 0.5 é double: numa conta entre int e double, o int é convertido (aula Conversão de Tipos). A TIPO é ótima para tirar dúvidas assim.
No editor do guia, alguns tipos mudam

O editor roda num computador de 32 bits (WebAssembly), em que long tem 4 bytes. Por isso um número grande como 3000000000, que não cabe em 4 bytes, é long no gcc do laboratório e long long no editor. O _Generic não erra: ele mostra o tipo de verdade em cada máquina. Veja a tabela de diferenças em O Editor do Guia.

Missão: rode: a macro diz que 2.5f é "desconhecido", porque não há caso para float. Acrescente a associação float: "float" na macro (não esqueça a vírgula e a barra \) para aparecer 2.5f é float.

#include <stdio.h>

#define TIPO(x) _Generic((x),   \
    int: "int",                 \
    double: "double",           \
    default: "desconhecido")

int main(void) {
    printf("7 é %s\n", TIPO(7));
    printf("2.5 é %s\n", TIPO(2.5));
    printf("2.5f é %s\n", TIPO(2.5f));
    return 0;
}
#include <stdio.h>

#define TIPO(x) _Generic((x),   \
    int: "int",                 \
    double: "double",           \
    float: "float",             \
    default: "desconhecido")

int main(void) {
    printf("7 é %s\n", TIPO(7));
    printf("2.5 é %s\n", TIPO(2.5));
    printf("2.5f é %s\n", TIPO(2.5f));
    return 0;
}

3. O caso default

O default é opcional. Mas, sem ele, um tipo que não está na lista é erro de compilação:

#include <stdio.h>

#define TIPO(x) _Generic((x), int: "int", double: "double")

int main(void) {
    float preco = 4.5f;
    printf("%s\n", TIPO(preco));
    return 0;
}
$ gcc -std=c23 -Wall -Wextra semdefault.c -o semdefault
semdefault.c: In function ‘main’:
semdefault.c:3:26: error: ‘_Generic’ selector of type ‘float’ is not compatible with any association
    3 | #define TIPO(x) _Generic((x), int: "int", double: "double")
      |                          ^
semdefault.c:7:20: note: in expansion of macro ‘TIPO’
    7 |     printf("%s\n", TIPO(preco));
      |                    ^~~~
‘_Generic’ selector of type ‘float’
"O seletor (a expressão de controle) do _Generic, de tipo float…"
is not compatible with any association
"…não combina com nenhuma associação": não há caso float: nem default:.
note: in expansion of macro ‘TIPO’
"Na expansão da macro TIPO": o erro está dentro da macro, mas quem a usou foi a linha 7. É lá que você descobre qual variável causou o problema.

O Clang (editor) escreve controlling expression type 'float' not compatible with any generic association type: "o tipo da expressão de controle, float, não é compatível com nenhum tipo das associações".

Isso é ruim? Depende. Na TIPO, que só mostra um nome, o default é bem-vindo. Numa função que calcula algo, muitas vezes é melhor não ter default: um erro de compilação é mais seguro do que fazer a conta errada em silêncio com um tipo que você não previu.


4. Escolher uma função: o imprimir(x)

Do lado direito de uma associação pode ir qualquer expressão, inclusive o nome de uma função. O nome de uma função, sem os parênteses, é um ponteiro para ela (aula Ponteiros para Funções). Então dá para o _Generic escolher a função e, logo depois, chamá-la com (x):

#include <stdio.h>

void imprimir_int(int x)            { printf("inteiro: %d\n", x); }
void imprimir_double(double x)      { printf("double: %.2f\n", x); }
void imprimir_texto(const char *x)  { printf("texto: %s\n", x); }
void imprimir_char(char x)          { printf("letra: '%c'\n", x); }

#define imprimir(x) _Generic((x),        \
    int: imprimir_int,                   \
    double: imprimir_double,             \
    char: imprimir_char,                 \
    char *: imprimir_texto,              \
    const char *: imprimir_texto)(x)

int main(void) {
    int macas = 6;
    double preco = 4.5;
    char inicial = 'A';
    imprimir(macas);
    imprimir(preco);
    imprimir(inicial);
    imprimir("Lojinha da Ana");
    return 0;
}
$ gcc -std=c23 -Wall -Wextra imprimir.c -o imprimir
$ ./imprimir
inteiro: 6
double: 4.50
letra: 'A'
texto: Lojinha da Ana

Siga o que o compilador faz com imprimir(preco):

  1. O pré-processador troca a macro: _Generic((preco), int: imprimir_int, double: imprimir_double, …)(preco).
  2. O compilador vê que preco é double e escolhe imprimir_double.
  3. Sobra imprimir_double(preco): uma chamada de função comum.
int: imprimir_int
Sem parênteses: é a função em si, não uma chamada. O _Generic escolhe qual função; quem chama é o (x) do fim.
)(x)
O primeiro ) fecha o _Generic; o (x) chama a função escolhida com o mesmo valor.
char *: e const char *:
Dois casos para a mesma função. char * e const char * são tipos diferentes para o _Generic: um texto entre aspas é char *; uma variável const char *nome é const char *. Com os dois, imprimir aceita as duas formas.

Para quem usa a macro, parece que existe uma única função imprimir que aceita qualquer tipo. Linguagens como C++ e Java chamam isso de sobrecarga (overloading): várias funções com o mesmo nome. O C não tem sobrecarga, e o _Generic é o jeito de imitá-la.

Experimente: chame imprimir('B'). Qual função foi escolhida, e por quê? (Lembre do que a TIPO mostrou sobre 'A'.) Depois tente imprimir(2.5f) e leia o erro: falta um caso para float. Acrescente float: imprimir_double e rode de novo.

#include <stdio.h>

void imprimir_int(int x)            { printf("inteiro: %d\n", x); }
void imprimir_double(double x)      { printf("double: %.2f\n", x); }
void imprimir_texto(const char *x)  { printf("texto: %s\n", x); }
void imprimir_char(char x)          { printf("letra: '%c'\n", x); }

#define imprimir(x) _Generic((x),        \
    int: imprimir_int,                   \
    double: imprimir_double,             \
    char: imprimir_char,                 \
    char *: imprimir_texto,              \
    const char *: imprimir_texto)(x)

int main(void) {
    char inicial = 'A';
    imprimir(inicial);
    imprimir(10);
    imprimir("caderno");
    return 0;
}

Se você chamou imprimir('B'), viu inteiro: 66: o 'B' é int, então a escolhida foi imprimir_int, que mostra o código do caractere.


5. O tgmath.h e um ABS(x) genérico

A biblioteca padrão já usa essa ideia. O math.h tem uma função para cada tipo: sqrt (raiz quadrada de double), sqrtf (de float), sqrtl (de long double). O cabeçalho tgmath.h C99 ("type-generic math", matemática genérica de tipo) troca esses nomes por macros que escolhem sozinhas:

#include <stdio.h>
#include <tgmath.h>

int main(void) {
    float f = 2.0f;
    double d = 2.0;
    printf("%zu bytes: %.7f\n", sizeof(sqrt(f)), sqrt(f));
    printf("%zu bytes: %.7f\n", sizeof(sqrt(d)), sqrt(d));
    printf("%.1f\n", fabs(-3.5f));
    return 0;
}
$ gcc -std=c23 -Wall -Wextra tg.c -o tg
/usr/bin/x86_64-linux-gnu-ld.bfd: /tmp/ccW3Fms3.o: na função "main":
tg.c:(.text+0x2e): referência não definida para "sqrtf"
/usr/bin/x86_64-linux-gnu-ld.bfd: tg.c:(.text+0x67): referência não definida para "sqrt"
collect2: error: ld returned 1 exit status
$ gcc -std=c23 -Wall -Wextra tg.c -o tg -lm      « -lm: liga a biblioteca de matemática
$ ./tg
4 bytes: 1.4142135
8 bytes: 1.4142136
3.5
  • O primeiro gcc falhou no ligador (referência não definida para "sqrtf", em inglês undefined reference to). Repare no nome: o seu código escreveu sqrt(f), mas o tgmath.h escolheu sqrtf. No Linux, as funções de matemática ficam numa biblioteca separada, e o -lm a inclui (aula Matemática). No editor do guia não precisa de -lm.
  • sqrt(f) deu um resultado de 4 bytes (float, 7 casas certas) e sqrt(d) um de 8 bytes (double). O mesmo nome, duas funções.
  • fabs(-3.5f) ("floating absolute", valor absoluto de ponto flutuante) virou fabsf.

E o abs de inteiros?

O tgmath.h cuida só de float, double, long double e números complexos. Para inteiros, cada tamanho continua tendo a sua função em stdlib.h: abs (int), labs (long), llabs (long long). Usar a errada é um erro clássico que o -Wall pega:

$ cat absruim.c
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    double temperatura = -2.5;
    printf("%d\n", abs(temperatura));
    return 0;
}
$ gcc -std=c23 -Wall -Wextra absruim.c -o absruim
absruim.c: In function ‘main’:
absruim.c:6:20: warning: using integer absolute value function ‘abs’ when argument is of floating-point type ‘double’ [-Wabsolute-value]
    6 |     printf("%d\n", abs(temperatura));
      |                    ^~~
$ ./absruim
2                         « a temperatura era -2.5: o ,5 sumiu

"Usando a função de valor absoluto de inteiro abs quando o argumento é de ponto flutuante double." Com o _Generic, você faz o seu ABS que escolhe a certa:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#define ABS(x) _Generic((x),   \
    int: abs,                  \
    long: labs,                \
    long long: llabs,          \
    float: fabsf,              \
    default: fabs)(x)

int main(void) {
    printf("%d\n", ABS(-7));
    printf("%ld\n", ABS(-7L));
    printf("%.2f\n", ABS(-2.5));
    printf("%.2f\n", ABS(-1.25f));
    return 0;
}
$ gcc -std=c23 -Wall -Wextra abs.c -o abs
$ ./abs
7
7
2.50
1.25

O default: fabs cobre o double (e qualquer outro tipo, que será convertido para double). Aqui não precisou de -lm: com números escritos direto no código, o gcc calcula o resultado durante a compilação e nem chama a biblioteca.


6. Formas diferentes e qual usar

Há dois jeitos comuns de escrever um imprimir genérico. O da seção 4 escolhe uma função. Outro, bem curto, escolhe só o formato do printf:

#include <stdio.h>

#define FORMATO(x) _Generic((x), int: "%d\n", double: "%.2f\n", char *: "%s\n")
#define mostrar(x) printf(FORMATO(x), (x))

int main(void) {
    mostrar(6);
    mostrar(4.5);
    mostrar("caderno");
    return 0;
}
$ gcc -std=c23 -Wall -Wextra formato.c -o formato
$ ./formato
6
4.50
caderno
FormaBom paraCuidado
_Generic devolvendo um texto (TIPO, FORMATO)Mostrar nomes, escolher o formato do printf. Curto.No mostrar, o x aparece duas vezes na macro: mostrar(i++) soma 1 (só uma vez, porque o FORMATO não executa o x), mas é fácil errar com macros assim.
_Generic escolhendo uma função (imprimir, ABS)Quando cada tipo precisa de um código diferente. O x é usado uma vez só.Precisa de uma função para cada tipo.
Uma função por tipo, sem _Generic (imprimir_int, imprimir_double)Código simples, fácil de ler e de depurar.Quem chama precisa escolher o nome certo.

Qual usar: no dia a dia, funções com nomes diferentes resolvem quase tudo, e é o que a maioria do código C faz. Use o _Generic quando um nome único deixa o uso bem mais seguro, como no ABS (que evita o abs num double) ou numa biblioteca que outras pessoas vão usar. E prefira a forma que escolhe uma função: ela é a mais previsível.


Quando dá errado

error: ‘_Generic’ selector of type ‘float’ is not compatible with any association

O que significa: o tipo da expressão não tem caso na lista, e não há default. No Clang: controlling expression type 'float' not compatible with any generic association type.

Como consertar: acrescente o caso que falta (float: …) ou um default: …. A note: in expansion of macro mostra a linha que usou a macro.

error: ‘_Generic’ specifies two compatible types

Aparece quando a lista tem dois nomes para o mesmo tipo:

$ cat dupla.c
#include <stdio.h>

int main(void) {
    int n = 3;
    printf("%s\n", _Generic(n, int: "int", signed int: "signed int"));
    return 0;
}
$ gcc -std=c23 -Wall -Wextra dupla.c -o dupla
dupla.c: In function ‘main’:
dupla.c:5:44: error: ‘_Generic’ specifies two compatible types
    5 |     printf("%s\n", _Generic(n, int: "int", signed int: "signed int"));
      |                                            ^~~~~~
dupla.c:5:32: note: compatible type is here
    5 |     printf("%s\n", _Generic(n, int: "int", signed int: "signed int"));
      |                                ^~~
dupla.c:5:44: error: ‘_Generic’ selector matches multiple associations
    5 |     printf("%s\n", _Generic(n, int: "int", signed int: "signed int"));
      |                                            ^~~~~~
dupla.c:5:32: note: other match is here
    5 |     printf("%s\n", _Generic(n, int: "int", signed int: "signed int"));
      |                                ^~~

O que significa: int e signed int são o mesmo tipo, então a recepcionista teria duas filas para o mesmo documento. Acontece também com typedef: se idade_t é um apelido de int, os dois não podem aparecer juntos.

Como consertar: deixe só um dos dois. Atenção: char, signed char e unsigned char são três tipos diferentes e podem conviver.

O ramo não escolhido também precisa compilar
$ cat ramo.c
#include <stdio.h>
#include <string.h>

#define TAMANHO(x) _Generic((x), char *: strlen(x), int: sizeof(x))

int main(void) {
    int n = 7;
    printf("%zu\n", TAMANHO(n));
    return 0;
}
$ gcc -std=c23 -Wall -Wextra ramo.c -o ramo
ramo.c: In function ‘main’:
ramo.c:8:29: error: passing argument 1 of ‘strlen’ makes pointer from integer without a cast [-Wint-conversion]
    8 |     printf("%zu\n", TAMANHO(n));
      |                             ^
      |                             |
      |                             int
ramo.c:4:49: note: in definition of macro ‘TAMANHO’
    4 | #define TAMANHO(x) _Generic((x), char *: strlen(x), int: sizeof(x))
      |                                                 ^
In file included from ramo.c:2:
/usr/include/string.h:439:35: note: expected ‘const char *’ but argument is of type ‘int’
  439 | extern size_t strlen (const char *__s)
      |                       ~~~~~~~~~~~~^~~

O que significa: o n é int, então o ramo escolhido é sizeof(x). Mesmo assim, o compilador confere todos os ramos com o x de verdade, e strlen(n) (passar um inteiro onde se espera um texto) é erro. O _Generic não "apaga" os outros ramos antes de conferir.

Como consertar: faça cada ramo ser só o nome de uma função e chame com (x) depois do _Generic, como no imprimir. Assim o x aparece numa chamada só, a da função escolhida.

O caso const int nunca é escolhido

Com const int limite = 10;, o programa _Generic(limite, int: "int", const int: "const int") mostra int. O gcc fica quieto; o Clang avisa: due to lvalue conversion of the controlling expression, association of type 'const int' will never be selected because it is qualified [-Wunreachable-code-generic-assoc].

O que significa: antes de escolher, o compilador tira o const (e o volatile) do tipo da variável: o que importa é o tipo do valor. Por isso um caso const int: nunca combina. Já const char * funciona, porque ali o const é do texto apontado, não do ponteiro.

Como consertar: escreva o caso sem const (int:).


Referência rápida

EscritaO que faz
_Generic(x, int: A, double: B)Vira A se x é int, B se é double; erro se for outro tipo
default: CCaso para os tipos que não estão na lista
#define TIPO(x) _Generic((x), …)O jeito normal de usar: dentro de uma macro
_Generic((x), int: f_int, double: f_double)(x)Escolhe uma função e a chama com x
'A'Tipo int em C (não char)
"Ana"Tipo char * no _Generic
const int na expressãoEscolhe o caso int (o const é ignorado)
#include <tgmath.h>sqrt, pow, fabs… se adaptam a float/double/long double
Versão_Generic existe desde o C11; tgmath.h desde o C99

Exercícios

1. Complete o caso

Complete para o _Generic virar "texto" quando x for uma string entre aspas:

_Generic((x), int: "número", : "texto")

2. O caso que pega o resto

Complete com a palavra que cria o caso para "qualquer outro tipo":

_Generic((x), int: "int", : "outro")

3. Com a macro TIPO da seção 2, o que TIPO('Z') mostra?

4. Depois de int i = 1; const char *t = _Generic(i++, int: "a", default: "b");, quanto vale i?

5. Quando o _Generic faz a escolha?

6. O que acontece com _Generic(x, int: "a", double: "b") se x é float?

Missão final: o imprimir da lojinha

Missão: a macro imprimir só conhece int, e por isso o programa nem compila. Complete o _Generic com os casos double (use imprimir_double) e char * (use imprimir_texto), para a saída ficar como abaixo, sem avisos.

inteiro: 6
double: 4.50
texto: Caderno
#include <stdio.h>

void imprimir_int(int x)            { printf("inteiro: %d\n", x); }
void imprimir_double(double x)      { printf("double: %.2f\n", x); }
void imprimir_texto(const char *x)  { printf("texto: %s\n", x); }

#define imprimir(x) _Generic((x),   \
    int: imprimir_int)(x)

int main(void) {
    imprimir(6);
    imprimir(4.5);
    imprimir("Caderno");
    return 0;
}
#include <stdio.h>

void imprimir_int(int x)            { printf("inteiro: %d\n", x); }
void imprimir_double(double x)      { printf("double: %.2f\n", x); }
void imprimir_texto(const char *x)  { printf("texto: %s\n", x); }

#define imprimir(x) _Generic((x),   \
    int: imprimir_int,              \
    double: imprimir_double,        \
    char *: imprimir_texto)(x)

int main(void) {
    imprimir(6);
    imprimir(4.5);
    imprimir("Caderno");
    return 0;
}

Desafio extra: acrescente float: imprimir_double e chame imprimir(1.5f). O float é convertido para double na chamada, e sai double: 1.50.


Resumo

  • _Generic(x, tipo: expressão, …) C11 escolhe uma expressão pelo tipo de x, na compilação. O x não é executado.
  • Quase sempre fica dentro de uma macro: #define TIPO(x) _Generic((x), …).
  • default: pega os tipos não listados. Sem ele, um tipo fora da lista é erro de compilação (o que às vezes é bom).
  • Escolhendo o nome de uma função e chamando com (x) depois, você imita a sobrecarga: imprimir(6), imprimir("Ana").
  • Em C, 'A' é int, "Ana" é char * e o const da variável é ignorado na escolha.
  • Todos os ramos têm de compilar com o x de verdade, mesmo os não escolhidos.
  • tgmath.h já faz sqrt, fabs e companhia se adaptarem ao tipo; para inteiros, um ABS(x) com _Generic escolhe entre abs, labs e llabs.