Les nouveautés C++20 pour améliorer les templates en C++

18 Nov 2021 18 Nov 2021 3502 vues ESSADDOUKI Mostafa 6 min de lecture
Introduction et syntaxe de base
1 Introduction au langage C++ 2 Entrée-sortie en C++ - cin et cout 3 Inférence de type avec le mot-clé auto en C++ 4 Classe std::string et les chaînes de caractères en C++ 5 Les structures conditionnelles (if et switch) en C++ (C++17 et C++20) 6 Les boucles en C++ (C++17 et C++20) 7 La gestion des fichiers en C++
Pointeurs et fonctions
8 Introduction aux pointeurs en C++ - Déclaration et interêts 9 Les références en C++ - déclaration et interêts 10 Les tableaux en C++ - Déclaration et interêts 11 Introduction aux fonctions en C++ 12 Passer des arguments à une fonction en C++ 13 Déclarer un paramètre const en C++ 14 Les fonctions Lambda en C++ 15 Fonctions utiles (Mathématiques et caractères) en C++
Programmation OO
16 Classes et objets en C++ 17 Spécificateurs d'accès en C++ 18 Constructeurs et destructeur d'une classe en C++ 19 Fonctions membres en C++ 20 Membres statiques d'une classe en C++ 21 Fonctions en ligne en C++ - inline 22 Fonctions et classes amies en C++ - friend 23 Surcharge des fonctions en C++ 24 Surcharge des opérateurs en C++ 25 Héritage en C++ 26 La gestion d'exceptions en C++ : déclaration, utilisation et personnalisation 27 fonctions et classes templates en C++ 28 Les nouveautés C++20 pour améliorer les templates en C++
Structures de données
29 Introduction aux structures de données 30 Les structures en C++ et la différence avec les structures en C 31 Les listes chaînées en C++ 32 Les piles en C++ 33 File d'attente en C++ 34 Arbre binaire de recherche : définition et mise en oeuvre en C++
La bibliothèque standard (STL)
35 Introduction à la bibliothèque de Template Standard STL 36 Les itérateurs en C++ - définition, déclaration et exemples 37 La classe array en C++ (bibliothèque STL) <array> 38 La classe vector de la bibliothèque STL <vector> 39 La classe deque en C++ ( Bibliothèque STL) 40 La classe list en C++ (bibliothèque STL) <list> 41 La classe stack (Pile) en C++ (bibliothèque STL) <stack> 42 La classe queue (File d'attente) en C++ (bibliothèque STL) <queue> 43 La file d'attente prioritaire (classe priority_queue) - Bibliothèque STL 44 Les ensembles en C++ (Classe set <set> - Bibliothèque STL) 45 Les dictionnaires en C++ : Classe map (Bibliothèque STL) 46 Introduction aux algorithmes de la bibliothèque STL (programmation compétitive) 47 Tri et méthodes associées en C++ - Bibliothèque STL 48 Recherche dichotomique et méthodes associées en C++ - Bibliothèque STL 49 Appliquer un prédicat ou une fonction aux éléments d'une séquence en C++ - Bibliothèque STL 50 Recherche dans une séquence et méthodes associées en C++ - Bibliothèque STL

Améliorations des templates en C++20

Les améliorations apportées aux templates rendent le C++20 plus cohérent et moins sujet aux erreurs lors de l'écriture de programmes génériques.

Constructeur conditionnellement explicite

Parfois, vous avez besoin d'une classe acceptant différents types en entrée. Par exemple, une classe DifferentType contenant un std::variant :

  Exemple 1 — Classe avec std::variant

class DifferentType {
    variant<bool, char, int, double, float, string> myVariant;
};

Pour initialiser un DifferentType avec différents types, une solution consiste à rendre le constructeur générique. Cependant, cela pose un problème : un constructeur générique est un catch-all — il accepte n'importe quel type.

explicit vs implicite
  • Sans explicit : les conversions implicites sont autorisées — on peut écrire Implicite imp = "texte".
  • Avec explicit : seuls les appels explicites sont valides — Explicite exp{"texte"}. Les conversions implicites sont interdites.

  Exemple 2 — Constructeur implicite vs explicite

#include <iostream>
#include <string>
using namespace std;

struct Implicite {
    template <typename T>
    Implicite(T t) {
        cout << t << '\n';
    }
};

struct Explicite {
    template <typename T>
    explicit Explicite(T t) {
        cout << t << '\n';
    }
};

int main() {
    Implicite imp1 = "implicite";   // conversion implicite : OK
    Implicite imp2("explicite");    // appel explicite : OK
    Implicite imp3 = 10.5;          // conversion implicite : OK
    Implicite imp4(10.5);           // appel explicite : OK

    cout << '\n';

    // Explicite exp1 = "implicit";  // ERREUR : conversion implicite interdite
    Explicite exp2{"explicite"};     // appel explicite : OK
    // Explicite exp3 = 2021;        // ERREUR : conversion implicite interdite
    Explicite exp4{2021};            // appel explicite : OK

    return 0;
}
Sortie
implicite
explicite
10.5
10.5

explicite
2021

explicit conditionnel (C++20)

En C++20, explicit peut être utilisé de manière conditionnelle : explicit(condition). Si la condition vaut true, le constructeur est explicite ; sinon, la conversion implicite reste autorisée.

is_same <T, U> is_same est un prédicat de compilation de la bibliothèque <type_traits>. Il est évalué à la compilation et retourne true si les types T et U sont identiques, false sinon.

  Exemple 3a — explicit conditionnel : conversions autorisées

#include <iostream>
#include <type_traits>
#include <typeinfo>
using namespace std;

struct Note {
    template <typename T>
    explicit(!is_same<T, double>::value) Note(T t) {
        cout << typeid(t).name() << '\n';
    }
};

void afficher(Note n) {}

int main() {
    Note n1(15.50);    // appel explicite avec double : OK
    Note n2 = 15.50;   // conversion implicite depuis double : OK (explicit = false)

    afficher(n1);
    afficher(17.5);    // conversion implicite depuis double : OK

    // afficher(true);      // ERREUR : bool != double → explicit = true
    // afficher("Bonsoir"); // ERREUR : const char* != double → explicit = true

    return 0;
}

  Exemple 3b — explicit conditionnel : erreurs de compilation

int main() {
    Note n1(15.50);
    Note n2 = 15.50;

    afficher(true);      // tentative de conversion implicite depuis bool
    afficher("Bonsoir"); // tentative de conversion implicite depuis const char*

    return 0;
}
Sortie — Erreurs de compilation
code.cpp:21:14: error: could not convert 'true' from 'bool' to 'Note'
   21 |     afficher(true);
      |              bool
code.cpp:22:14: error: could not convert '(const char*)"Bonsoir"' from 'const char*' to 'Note'
   22 |     afficher("Bonsoir");
      |              const char*
Explication explicit(!is_same<T, double>::value) rend le constructeur explicite pour tout type différent de double. Seule la conversion implicite depuis double est donc autorisée.

Paramètres de template sans type

C++ permet d'utiliser des non-types comme paramètres de template. Avant C++20, les non-types supportés étaient :

  • entiers et énumérateurs
  • pointeurs vers des objets, des fonctions et des attributs de classe
  • références lvalue
  • std::nullptr_t

C++20 étend cette liste en ajoutant le support des types à virgule flottante, des types littéraux et des littéraux de chaîne de caractères.

Types à virgule flottante

  Exemple 4 — Paramètres de template à virgule flottante

#include <iostream>
#include <typeinfo>
using namespace std;

template <double d>
auto getDouble() {
    return d;
}

template <auto NonType>
auto getNonType() {
    return NonType;
}

int main() {
    auto d1 = getDouble<5.5>();   // spécialisation pour 5.5
    auto d2 = getDouble<6.5>();   // spécialisation pour 6.5

    auto i = getNonType<2017>();
    cout << i << " " << typeid(i).name() << '\n';   // int

    auto f = getNonType<2020.1f>();
    cout << f << " " << typeid(f).name() << '\n';   // float

    auto d = getNonType<2020.2>();
    cout << d << " " << typeid(d).name();            // double

    return 0;
}
Sortie
2017 i
2020.1 f
2020.2 d
Spécialisation automatique Chaque appel de getDouble<valeur>() génère une nouvelle fonction spécialisée pour la valeur donnée. Depuis C++17, le paramètre auto permet de déduire le type du non-type. C++20 étend cette déduction aux types à virgule flottante.

Types littéraux

Un type littéral satisfait deux propriétés :

  • Toutes ses classes de base et membres de données non statiques sont publics et non modifiables.
  • Tous ces membres sont de types structurels ou des tableaux de ceux-ci.
Condition requise Un type littéral doit posséder un constructeur constexpr pour pouvoir être utilisé comme paramètre de template sans type.

  Exemple 5 — Type littéral comme paramètre de template

struct NewType {
    constexpr NewType(int) {}
};

template <NewType cl>
auto getClassType() {
    return cl;
}

int main() {
    auto c1 = getClassType<NewType(2021)>();
    return 0;
}

Littéraux de chaîne de caractères

  Exemple 6 — Chaîne comme paramètre de template sans type

#include <iostream>
using namespace std;

template <int N>
class StringLiteral {
public:
    char data[N];

    constexpr StringLiteral(char const (&str)[N]) {
        copy(str, str + N, data);
    }
};

template <StringLiteral str>
class ClasseTemplate {};

template <StringLiteral str>
void FunctionTemplate() {
    cout << str.data << '\n';
}

int main() {
    ClasseTemplate<"chaine de caracteres"> cls;
    FunctionTemplate<"chaine de caracteres">();
    return 0;
}
Sortie
chaine de caracteres
Explication StringLiteral est un type littéral grâce à son constructeur constexpr (ligne 9) qui prend une chaîne C en paramètre. Il peut ainsi être utilisé comme paramètre de template sans type pour ClasseTemplate et FunctionTemplate.

Discussion (0)

Soyez le premier à laisser un commentaire !

Laisser un commentaire

Votre commentaire sera visible après modération.