Maîtriser les évolutions majeures de la norme C++17

Expressions de repli (Fold Expressions)

L'une des avancées les plus significatives de C++17 pour la métaprogrammation est l'introduction des expressions de repli. Elles simplifient drastiquement la manipulation des packs de paramètres dans les templates variadiques, évitant ainsi le recours systématique à la récursion de templates.

Voici comment implémenter une fonction d'accumulation générique avec cette syntaxe épurée :

template<typename... Args>
auto calculerSomme(Args... valeurs) {
    return (valeurs + ...); // Repli unaire à droite
}

// Utilisation
int total = calculerSomme(10, 20, 30, 40); // Résultat : 100

Il est également possible d'utiliser d'autres opérateurs, comme l'opérateur logique ET pour vérifier une série de conditions :

template<typename... T>
bool verifierTout(T... conditions) {
    return (conditions && ...);
}

bool estValide = verifierTout(true, (5 > 2), (10 != 0)); // true

Liaisons structurées (Structured Bindings)

Les liaisons structurées permettent de décomposer des objets complexes (paires, tuples, tableaux ou structures) en variables individuelles de manière concise. Cela améliore la lisibilité en éliminant l'utilisation de std::get ou des accès par index.

#include <iostream>
#include <map>
#include <string>

struct Employe {
    int id;
    std::string nom;
    double salaire;
};

int main() {
    Employe dev = {101, "Alice", 75000.0};

    // Décomposition de la structure
    auto [identifiant, nomUtilisateur, remuneration] = dev;

    std::cout << "Employé : " << nomUtilisateur << " (ID: " << identifiant << ")" << std::endl;

    // Utilisation pratique dans une boucle sur une map
    std::map<int, std::string> inventaire = {{1, "Clavier"}, {2, "Souris"}};
    for (const auto& [code, produit] : inventaire) {
        std::cout << "Code : " << code << ", Produit : " << produit << std::endl;
    }
    return 0;
}

Variables statiques "inline"

Avant C++17, l'initialisation de variables statiques non entières dans un fichier d'en-tête était complexe et nécessitait souvent un fichier source séparé pour éviter les erreurs de définition multiple (ODR). Le mot-clé inline appliqué aux variables permet désormais de définir et d'initialiser des membres statiques directement dans la classe.

class ConfigurationSysteme {
public:
    static inline const std::string NomApplication = "MoteurRenduV3";
    static inline int VersionMajeure = 3;
    static inline double SeuilTolerance = 0.001;
};

Le type std::optional

Le conteneur std::optional représente une valeur qui peut ou non exister. C'est une alternative robuste à l'utilisation de pointeurs nuls ou de codes d'erreur sentinellles (comme -1 ou une chaîne vide).

#include <optional>
#include <string>

std::optional<std::string> recupererPseudo(int userId) {
    if (userId == 7) return "Admin_Alpha";
    return std::nullopt; // Aucune valeur
}

void traiter() {
    auto resultat = recupererPseudo(7);
    if (resultat.has_value()) {
        std::string nom = *resultat; // Accès sécurisé
    }
    
    // Utilisation de value_or pour une valeur par défaut
    std::string pseudoFinal = recupererPseudo(99).value_or("Anonyme");
}

Le conteneur std::any

std::any est un cnoteneur de type sécurisé capable de stocker une valeur de n'importe quel type copie-constructible. Contrairement à void*, il conserve l'information de type et permet une récupération sécurisée via std::any_cast.

#include <any>
#include <vector>

void demoAny() {
    std::any stockage;

    stockage = 500; // Stocke un int
    int valeur = std::any_cast<int>(stockage);

    stockage = std::string("C++17"); // Stocke une chaîne
    if (stockage.type() == typeid(std::string)) {
        auto texte = std::any_cast<std::string>(stockage);
    }
}

Attributs de compilation : nodiscard, fallthrough et maybe_unused

C++17 introduit des attributs standardisés pour aider le compilateur à détecter des erreurs potentielles ou pour supprimer des avertissements inutiles.

[[nodiscard]] : Indique que la valeur de retour d'une fonction ne doit pas être ignorée.

[[nodiscard]] bool ouvrirFichier() {
    // ... logique
    return true;
}

// Un appel sans récupérer le booléen générera un avertissement.

[[fallthrough]] : Indique que l'absence de break dans un switch est intentionnelle.

switch (mode) {
    case 1:
        initialiserBase();
        [[fallthrough]]; // Le compilateur ne signalera pas d'erreur
    case 2:
        executerTraitement();
        break;
}

[[maybe_unused]] : Supprime les avertissements si une variable n'est pas utilisée (utile pour le débogage ou les interfaces imposées).

void logger(int id, [[maybe_unused]] std::string metadata) {
#ifdef DEBUG
    std::cout << metadata;
#endif
}

Étiquettes: cpp17 metaprogramming std-optional structured-bindings fold-expressions

Publié le 21 juillet à 03h34