C++23 marque un énorme bond en avant en matière d'ergonomie quotidienne, de propagation d'erreurs plus sûre et d'outillage à la compilation. Allant au-delà des paradigmes fondateurs introduits par C++20, cette norme se concentre fortement sur des codebases plus propres, plus rapides et plus expressives.
Voici les 15 fonctionnalités essentielles que tout développeur C++ devrait exploiter dans ses projets modernes.
1. std::print et std::println
L'affichage formaté devient enfin une instruction native et type-safe de la bibliothèque standard. Fini les jongleries avec les spécificateurs printf historiques ou les iostreams verbeux.
#include <print>
int main() {
std::println("Hello, {}! Codebase health is: {:.1f}%", "C++23", 98.5);
}
- Pourquoi c'est important : chaînes de format vérifiées à la compilation, débit supérieur et journalisation plus propre sur les chemins critiques.
2. std::expected
Apporte les erreurs typées sous forme de valeurs en C++, permettant à une fonction de retourner soit un résultat valide, soit une erreur inattendue, sans lever d'exception.
#include <expected>
#include <string>
std::expected<int, std::string> parseConfig(const std::string& path) {
if (path.empty()) return std::unexpected("Invalid path");
return 42; // parsed value
}
- Pourquoi c'est important : idéal pour les modules basse latence, les interfaces de plugins et les frontières d'ABI où les exceptions introduisent un surcoût ou des problèmes de sécurité.
3. Deducing this (paramètre objet explicite)
Résout un problème vieux de dix ans pour les auteurs de bibliothèques en permettant aux fonctions membres de déduire leur propre catégorie de valeur et leur const-ness.
struct Builder {
std::string buffer;
auto append(this auto&& self, std::string_view text) {
self.buffer.append(text);
return std::forward<decltype(self)>(self); // Fluent chaining
}
};
- Pourquoi c'est important : élimine les surcharges dupliquées pour les fonctions membres const, non-const, lvalue et rvalue.
4. Les ranges montent en puissance : std::views::zip, chunk et slide
C++20 a introduit les ranges, mais C++23 les surbooste avec des adaptateurs multi-séquences.
#include <ranges>
#include <vector>
#include <iostream>
std::vector<int> keys = {1, 2, 3};
std::vector<std::string> values = {"one", "two", "three"};
auto zipped = std::views::zip(keys, values);
- Pourquoi c'est important : remplace les boucles fragiles basées sur des indices par des pipelines de données corrects par construction.
5. std::ranges::to
Un moyen élégant de convertir n'importe quelle vue de range directement en conteneur standard.
#include <ranges>
#include <vector>
#include <set>
std::vector<int> v = {1, 2, 2, 3};
auto s = v | std::views::filter([](int x){ return x > 1; })
| std::ranges::to<std::set>();
- Pourquoi c'est important : termine proprement les pipelines d'évaluation paresseuse sans boilerplate d'initialisation manuelle de conteneur.
6. std::stacktrace
Des stack traces portables standardisées, directement disponibles.
#include <stacktrace>
#include <print>
void debugLog() {
std::println("Current execution path:\n{}", std::stacktrace::current());
}
- Pourquoi c'est important : accélère considérablement le triage d'incidents et le reporting d'erreurs sans dépendre d'API spécifiques à la plateforme (DBGHELP / backtrace).
7. Opérations monadiques pour std::optional
std::optional gagne and_then, transform et or_else, atteignant la puissance expressive de std::expected.
#include <optional>
std::optional<std::string> getUsername(int id);
auto length = getUsername(10)
.transform([](const std::string& name) { return name.length(); });
- Pourquoi c'est important : supprime complètement l'enfer des pyramides de if (opt.has_value()).
8. if consteval
Distingue proprement l'évaluation à la compilation de l'exécution à l'exécution.
constexpr int compute(int x) {
if consteval {
return fastCompileTimeAlgorithm(x);
} else {
return optimizedRuntimeSIMD(x);
}
}
- Pourquoi c'est important : simplifie l'écriture de fonctions à double chemin optimisées à la fois pour l'évaluation à la compilation et les performances à l'exécution.
9. auto(x) et auto{x} (copie dégradée d'expressions)
Crée une copie temporaire dégradée d'une expression, en ligne.
auto process = [ptr = auto(*expensiveRef)]() {
// captures a decayed copy safely
};
- Pourquoi c'est important : prévient les références pendantes dans les lambdas et aux frontières des tâches asynchrones.
10. std::generator (les coroutines deviennent pratiques)
Fournit un type de générateur standard, asynchrone et paresseux, pour les coroutines C++.
#include <generator>
std::generator<int> fibonacci() {
int a = 0, b = 1;
while (true) {
co_yield a;
auto next = a + b;
a = b;
b = next;
}
}
- Pourquoi c'est important : simplifie le traitement de flux et la génération de séquences sans lourd boilerplate maison.
11. std::byteswap
Un utilitaire standard portable pour inverser l'ordre des octets.
#include <bit>
uint32_t val = 0x12345678;
uint32_t swapped = std::byteswap(val); // 0x78563412
- Pourquoi c'est important : essentiel pour les protocoles réseau, le parsing de fichiers binaires et la sérialisation bas niveau multiplateforme.
12. std::to_underlying
Convertit un type énuméré directement vers son type sous-jacent, en toute sécurité.
enum class Status : uint8_t { Ok = 0, Error = 1 };
auto val = std::to_underlying(Status::Error); // returns uint8_t(1)
- Pourquoi c'est important : remplace les casts explicites maladroits (static_cast<std::underlying_type_t<Status>>(val)) par une intention claire.
13. std::string::contains et std::string_view::contains
Vérifie directement l'existence d'une sous-chaîne ou d'un caractère.
#include <string>
std::string url = "https://cppdepend.com";
if (url.contains("https")) {
// Secure URL handled cleanly
}
- Pourquoi c'est important : élimine l'idiome verbeux str.find(...) != std::string::npos.
14. std::mdspan multidimensionnel
Une vue multidimensionnelle non propriétaire sur des blocs de données contigus.
#include <mdspan>
#include <vector>
std::vector<double> data(12);
std::mdspan matrix(data.data(), 3, 4); // 3x4 grid view
matrix[1, 2] = 5.0; // Zero allocation overhead
- Pourquoi c'est important : parfait pour le calcul intensif, la manipulation d'images et les moteurs de jeu, sans imposer de layout d'allocation mémoire.
15. std::unreachable (gestion explicite des branches impossibles)
Permet de marquer les branches inatteignables pour aider le compilateur à optimiser agressivement.
#include <utility>
void handleColor(Color c) {
switch(c) {
case Color::Red: break;
case Color::Blue: break;
default: std::unreachable(); // Tells optimizer this path is impossible
}
}
- Pourquoi c'est important : débloque des optimisations avancées de branches par le compilateur et élimine les chemins de vérification morts à l'exécution.
Gardez votre codebase modernisée en bonne santé
Adopter les fonctionnalités C++23 modernise votre codebase — mais une modernisation à grande échelle exige des garde-fous. Quels fichiers utilisent encore des logs façon printf ? Cette nouvelle frontière std::expected a-t-elle introduit une dépendance architecturale non voulue ? La complexité cyclomatique grimpe-t-elle dans votre nouveau code à coroutines ?
CppDepend répond à ces questions automatiquement : interrogez votre codebase avec CQLinq pour trouver chaque pattern legacy à migrer, suivez la dette technique au fil de la modernisation, et visualisez l'impact de chaque changement en 3D avec Smart Code City — comme exploré dans nos articles précédents :
