C++20 : les quatre grandes nouveautés et plus encore
Découvrez les fonctionnalités transformatrices de C++20 : concepts, ranges, opérateur spaceship, std::format, initialiseurs désignés, std::span, et plus encore.
C++20 : un bond générationnel
C++20 est la mise à jour la plus importante de C++ depuis C++11. Il introduit quatre fonctionnalités majeures — les Concepts, les Ranges, les Coroutines et les Modules — qui changent chacune en profondeur la façon d'écrire du C++. Au-delà de ces « quatre grandes », C++20 apporte aussi l'opérateur spaceship, std::format, std::span, les initialiseurs désignés et de nombreuses améliorations de constexpr. Cette leçon couvre tout sauf les coroutines et les modules, qui ont chacun leur propre leçon.
Concepts : contraindre les templates
Les concepts remplacent SFINAE par des contraintes lisibles et composables sur les paramètres de template. Ils produisent des messages d'erreur clairs lorsque les contraintes ne sont pas satisfaites :
#include <iostream>
#include <concepts>
#include <string>
#include <vector>
#include <numeric>
// Define a custom concept
template<typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;
// Use concept in a requires clause
template<typename T>
requires Numeric<T>
T add(T a, T b) {
return a + b;
}
// Shorthand syntax — concept as type constraint
auto multiply(Numeric auto a, Numeric auto b) {
return a * b;
}
// Concept with multiple requirements
template<typename Container>
concept Summable = requires(Container c) {
{ c.begin() } -> std::input_or_output_iterator;
{ c.end() } -> std::input_or_output_iterator;
{ c.size() } -> std::convertible_to<std::size_t>;
};
template<Summable Container>
auto sum_container(const Container& c) {
using T = typename Container::value_type;
return std::accumulate(c.begin(), c.end(), T{});
}
int main() {
std::cout << add(3, 4) << "\n"; // 7
std::cout << multiply(2.5, 4.0) << "\n"; // 10.0
std::vector<int> v{1, 2, 3, 4, 5};
std::cout << sum_container(v) << "\n"; // 15
// add(std::string("a"), std::string("b")); // Compile error!
// Error message: constraints not satisfied — Numeric<std::string> is false
return 0;
}Ranges : des algorithmes composables
Les ranges apportent à la STL une approche fonctionnelle, sous forme de pipeline. Les vues sont paresseuses — elles n'allouent ni ne copient de données, et composent des transformations qui ne s'exécutent qu'au moment de l'itération :
#include <iostream>
#include <ranges>
#include <vector>
#include <string>
#include <algorithm>
int main() {
std::vector<int> numbers{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Pipeline: filter even, square them, take first 3
auto result = numbers
| std::views::filter([](int n) { return n % 2 == 0; })
| std::views::transform([](int n) { return n * n; })
| std::views::take(3);
for (int n : result) {
std::cout << n << " "; // 4 16 36
}
std::cout << "\n";
// Range-based algorithms with projections
struct Employee {
std::string name;
int salary;
};
std::vector<Employee> team{
{"Alice", 90000}, {"Bob", 75000}, {"Carol", 120000}
};
// Sort by salary using a projection — no custom comparator needed
std::ranges::sort(team, {}, &Employee::salary);
for (const auto& e : team) {
std::cout << e.name << ": " << e.salary << "\n";
}
// iota generates an infinite sequence
for (int i : std::views::iota(1) | std::views::take(5)) {
std::cout << i << " "; // 1 2 3 4 5
}
std::cout << "\n";
return 0;
}Comparaison à trois voies et initialiseurs désignés
L'opérateur spaceship <=> génère les six opérateurs de comparaison à partir d'une seule déclaration. Les initialiseurs désignés importent du C l'initialisation des champs par leur nom, avec en plus la sûreté de typage du C++ :
#include <iostream>
#include <compare>
#include <string>
struct Version {
int major;
int minor;
int patch;
// One operator generates ==, !=, <, >, <=, >=
auto operator<=>(const Version&) const = default;
};
struct Config {
std::string host = "localhost";
int port = 8080;
bool ssl = false;
int max_connections = 100;
};
int main() {
Version v1{2, 0, 0};
Version v2{1, 9, 5};
if (v1 > v2) std::cout << "v1 is newer\n"; // true
if (v1 != v2) std::cout << "versions differ\n";
// Strong ordering gives you all 6 operators for free
auto cmp = v1 <=> v2;
if (cmp > 0) std::cout << "v1 > v2\n";
// Designated initializers — name the fields you're setting
Config cfg{
.host = "example.com",
.port = 443,
.ssl = true
// .max_connections keeps its default value (100)
};
std::cout << cfg.host << ":" << cfg.port
<< " SSL=" << std::boolalpha << cfg.ssl
<< " max=" << cfg.max_connections << "\n";
return 0;
}std::format et std::span
std::format apporte au C++ un formatage de chaînes à la Python. std::span est une vue non propriétaire sur une séquence contiguë d'éléments, qui remplace les paires pointeur brut + taille :
#include <iostream>
#include <format>
#include <span>
#include <vector>
#include <array>
// std::span — works with any contiguous container
void print_values(std::span<const int> data) {
for (int v : data) {
std::cout << std::format("{:>5}", v);
}
std::cout << "\n";
}
int main() {
// std::format — type-safe, positional formatting
std::string msg = std::format("Hello, {}! You are {} years old.", "Alice", 30);
std::cout << msg << "\n";
// Format specifiers
std::cout << std::format("Hex: {:#x}, Oct: {:#o}, Bin: {:#b}\n", 255, 255, 255);
std::cout << std::format("Pi: {:.4f}\n", 3.14159265);
std::cout << std::format("{:<15} {:>10}\n", "Name", "Score");
std::cout << std::format("{:<15} {:>10}\n", "Alice", 95);
// std::span — unifies arrays, vectors, and raw buffers
int c_array[] = {1, 2, 3, 4, 5};
std::vector<int> vec{6, 7, 8, 9, 10};
std::array<int, 3> arr{11, 12, 13};
print_values(c_array); // works with C array
print_values(vec); // works with vector
print_values(arr); // works with std::array
// Subspan — slice without copying
std::span<int> full(vec);
auto first_three = full.subspan(0, 3);
print_values(first_three); // 6 7 8
return 0;
}Améliorations de constexpr et chrono
C++20 étend massivement ce qui peut être constexpr : fonctions virtuelles, dynamic_cast, try-catch, et même std::vector et std::string peuvent désormais être utilisés dans des contextes constexpr. Davantage de calculs passent donc à la compilation, ce qui produit un code plus rapide à l'exécution.
La bibliothèque se dote d'un système complet de calendrier et de fuseaux horaires. Vous pouvez représenter des dates comme 2024y/January/15, faire de l'arithmétique calendaire et convertir entre fuseaux horaires — le tout avec sûreté de typage et sans bibliothèque externe.
Attributs : [[likely]] et [[unlikely]] indiquent au compilateur quelles branches sont les chemins les plus fréquents, ce qui permet une meilleure génération de code dans les sections critiques pour les performances.
Inutile d'utiliser tout C++20 d'un coup. Commencez par les fonctionnalités qui offrent le plus gros gain de productivité avec le moins de perturbations :
1. Opérateur spaceship — ajoutez auto operator<=>(const T&) const = default; à vos types dès maintenant
2. Concepts — utilisez les concepts standard (std::integral, std::ranges::range) avant d'écrire les vôtres
3. std::format — remplacez le formatage par printf et stringstream
4. Ranges — commencez par std::views::filter et std::views::transform dans le nouveau code
5. Initialiseurs désignés — utilisez-les pour les structs de configuration et les types d'options
Gardez les coroutines et les modules pour le moment où votre équipe et votre chaîne d'outils seront prêtes.
- Les concepts remplacent SFINAE par des contraintes de template lisibles et composables, avec des messages d'erreur clairs
- Les ranges fournissent des pipelines paresseux et composables grâce à l'opérateur
|— sans conteneurs temporaires - L'opérateur spaceship
<=>génère les six opérateurs de comparaison à partir d'une seule déclaration par défaut std::formatapporte au C++ un formatage de chaînes sûr au niveau des types, à la Pythonstd::spanest une vue non propriétaire sur de la mémoire contiguë, qui remplace les paires pointeur + taille- Les initialiseurs désignés (
{.field = value}) rendent la construction des structs auto-documentée
Quiz — Testez vos connaissances
(20 XP)1. Que fait `auto operator<=>(const MyType&) const = default;` ?
2. Quel est le principal avantage des vues de Ranges (comme `std::views::filter`) ?
3. Quel problème les concepts de C++20 résolvent-ils ?