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.
- O que o
_Genericfaz 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'éintem 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 fazsqrtefabsse adaptarem ao tipo, e o seu próprioABS(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ção | No _Generic |
|---|---|
| O documento entregue | A 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
_Genericinteiro "vira" a expressão escolhida. Na linha 7, é como se estivesse escritoconst 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_Genericcomalgono lugar dex. - \ 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 deint(aula Booleanos). - char *: "char *"
- Um texto entre aspas é um array de
char, mas no_Genericele 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ãochar. Em C, um caractere entre aspas simples tem tipoint(vale 65). Só a variávelletra, declarada comochar, échar. (Em C++ seriachar: é uma das diferenças entre as duas linguagens.)1.65édouble; com ofno fim,1.65f, éfloat(aula float e double).idade + 0.5édouble: numa conta entreintedouble, ointé convertido (aula Conversão de Tipos). ATIPOé ótima para tirar dúvidas assim.
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 tipofloat…" - is not compatible with any association
- "…não combina com nenhuma associação": não há caso
float:nemdefault:. - 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):
- O pré-processador troca a macro:
_Generic((preco), int: imprimir_int, double: imprimir_double, …)(preco). - O compilador vê que
precoédoublee escolheimprimir_double. - 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
_Genericescolhe 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 *econst char *são tipos diferentes para o_Generic: um texto entre aspas échar *; uma variávelconst char *nomeéconst char *. Com os dois,imprimiraceita 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
gccfalhou no ligador (referência não definida para "sqrtf", em inglês undefined reference to). Repare no nome: o seu código escreveusqrt(f), mas otgmath.hescolheusqrtf. No Linux, as funções de matemática ficam numa biblioteca separada, e o-lma 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) esqrt(d)um de 8 bytes (double). O mesmo nome, duas funções.fabs(-3.5f)("floating absolute", valor absoluto de ponto flutuante) viroufabsf.
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
| Forma | Bom para | Cuidado |
|---|---|---|
_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.
$ 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.
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
| Escrita | O que faz |
|---|---|
_Generic(x, int: A, double: B) | Vira A se x é int, B se é double; erro se for outro tipo |
default: C | Caso 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ão | Escolhe 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:
2. O caso que pega o resto
Complete com a palavra que cria o caso para "qualquer outro tipo":
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 dex, na compilação. Oxnã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 oconstda variável é ignorado na escolha. - Todos os ramos têm de compilar com o
xde verdade, mesmo os não escolhidos. tgmath.hjá fazsqrt,fabse companhia se adaptarem ao tipo; para inteiros, umABS(x)com_Genericescolhe entreabs,labsellabs.