Licence CC BY-SA

Les sélections génériques

Avant de nous atteler au dernier TP de ce cours, voyons ensemble une construction assez particulière ajoutée par la norme C11 : les sélections génériques.

Définition et utilisation

Les sélections génériques sont assez singulières en ce qu’elles permettent de construire des expressions en fonction du type d’une autre expression. Syntaxiquement, elles se rapprochent d’un mélange entre l’opérateur conditionnel et le switch.

_Generic(expression, type1: expression1, type2: expression2, default: expression3)

Dans cet exemple, le type de l’expression expression est évalué et, suivant celui-ci, la sélection générique est « remplacée » (le terme est impropre, mais il illustre bien le résultat obtenu) par expression1, expression2 ou expression3. Comme pour les switch, il est possible d’employer le mot-clé default en lieu et place d’un type afin de signaler que si aucun type ne correspond à celui de l’expression expression, alors c’est l’expression désignée par le mot-clé default qui doit être choisie.

Un exemple valant mieux qu’un long discours, voici un exemple de sélection générique affichant le type de son expression de contrôle.

#include <stdio.h>


int
main(void)
{
    int x;

    printf("type de x : %s\n", _Generic(x, char: "char", short: "short", int: "int", double: "double", default: "?"));
    return 0;
}
Résultat
type de x : int

Comme vous le voyez, la sélection générique sera « remplacée » par une des chaînes de caractères de la sélection suivant le type de la variable x, dans ce cas ci "int". Ce genre de sélections est toutefois plus utile au sein d’une macrofonction afin de pouvoir être réutilisée.

#include <stdio.h>

#define TYPEOF(x) _Generic((x), \
    char: "char", \
    short: "short", \
    int: "int", \
    long: "long", \
    long long: "long long", \
    float: "float", \
    double: "double", \
    long double: "long double")


int
main(void)
{
    int x;

    printf("type de x : %s\n", TYPEOF(x));
    return 0;
}

Néanmoins, une des grandes forces et particularités des sélections génériques tient au fait qu’elles peuvent être employées comme n’importe quel élément d’une expression (du moment que le résultat final est valide, bien entendu). Ainsi, une sélection générique peut finalement produire une constante (comme nous l’avons démontré ci-dessus), mais également un identificateur de variable ou de fonction.

#include <errno.h>
#include <limits.h>
#include <stdio.h>

#define add(x, y) _Generic((x) + (y), int: safe_add, long: safe_addl)((x), (y))


int safe_add(int a, int b)
{
    int err = 1;

    if (b >= 0 && a > INT_MAX - b)
        goto overflow;
    else if (b < 0 && a < INT_MIN - b)
        goto underflow;

    return a + b;
underflow:
    err = -1;
overflow:
    errno = ERANGE;
    return err > 0 ? INT_MAX : INT_MIN;
}


long safe_addl(long a, long b)
{
    int err = 1;

    if (b >= 0 && a > LONG_MAX - b)
        goto overflow;
    else if (b < 0 && a < LONG_MIN - b)
        goto underflow;

    return a + b;
underflow:
    err = -1;
overflow:
    errno = ERANGE;
    return err > 0 ? LONG_MAX : LONG_MIN;
}


int
main(void)
{
    printf("%d\n", add(INT_MAX, 45));
    printf("%ld\n", add(LONG_MAX, 1));
    return 0;
}
Résultat
2147483647
9223372036854775807

Dans l’exemple ci-dessus, nous avons repris la fonction safe_add() du chapitre sur les limites des types et avons ajouté la fonction safe_addl() qui est la même, mais pour le type long. Nous utilisons ensuite une sélection générique pour appeler la bonne fonction suivant le type de l’expression x + y. Vous remarquerez que dans ce cas ci, la sélection générique est « remplacée » par un identificateur de fonction (safe_add ou safe_addl) et que ce dernier peut être utilisé comme n’importe quel autre identificateur de fonction, par exemple pour appeler une fonction en lui précisant des arguments.

Évaluation

L’expression de contrôle d’une sélection générique n’est pas évaluée. Autrement dit, dans l’exemple précédent, l’addition n’est pas effectuée, seul son type est déterminé.

Si aucune entrée de la sélection générique ne correspond au type de l’expression de contrôle et qu’il n’y a pas d’entrée default, alors la compilation échouera.


En résumé
  1. Une sélection générique permet de construire une expression en fonction du type d’une autre expression.
  2. Il est possible d’utiliser le mot-clé default pour définir une expression à utiliser en cas de non correspondance des autres types.
  3. Une sélection générique peut être utilisée comme n’importe quel élément d’une expression.
  4. Dans le cas où il n’y a pas de correspondance entre le type de l’expression de contrôle et les entrées de la sélection et que le mot-clé default n’a pas été utilisé, la compilation échoue.