C++20 Ranges : un nouveau paradigme
Découvrez les ranges C++20 — une refonte moderne de l'interface d'algorithmes de la STL. Apprenez les concepts de range, les algorithmes basés sur les ranges, les projections et l'opérateur pipe pour des transformations de données composables.
Pourquoi les Ranges ?
Les Ranges C++20 sont une amélioration fondamentale du modèle d'algorithmes de la STL. La STL traditionnelle nécessite de passer des paires d'itérateurs à chaque algorithme, ce qui est verbeux et sujet aux erreurs (vous pouvez accidentellement passer des itérateurs de conteneurs différents). Les Ranges résolvent cela en vous permettant de passer le conteneur lui-même — ou tout objet qui définit begin() et end().
Améliorations clés par rapport aux algorithmes traditionnels :
1. Passez les conteneurs directement — std::ranges::sort(vec) au lieu de std::sort(vec.begin(), vec.end())
2. Projections — transformez les éléments avant la comparaison, sans les modifier
3. Concepts — les algorithmes ont des exigences claires à la compilation
4. Composabilité — chaînez les opérations avec l'opérateur pipe |
5. Évaluation paresseuse — les views calculent les éléments à la demande, évitant les copies intermédiaires
Concepts de Range
C++20 définit une hiérarchie de concepts de range dans :
- std::ranges::range — possède begin() et end()
- std::ranges::sized_range — possède size() en O(1)
- std::ranges::input_range — peut être lu une seule fois (comme les itérateurs d'entrée)
- std::ranges::forward_range — parcours avant multi-passe
- std::ranges::bidirectional_range — avant + arrière
- std::ranges::random_access_range — bidirectionnel + sauts en O(1)
- std::ranges::contiguous_range — accès aléatoire + mémoire contiguë
- std::ranges::common_range — begin() et end() retournent le même type
- std::ranges::viewable_range — peut être converti en view
Ces concepts sont utilisés pour contraindre les algorithmes basés sur les ranges, vous donnant des messages d'erreur clairs lorsque vous passez un range incompatible.
Algorithmes basés sur les Ranges
Les algorithmes basés sur les ranges vivent dans std::ranges:: et acceptent des conteneurs entiers. Ils retournent aussi des types de résultat plus riches au lieu de simples itérateurs.
#include <algorithm>
#include <ranges>
#include <vector>
#include <string>
#include <iostream>
struct Employee {
std::string name;
int age;
double salary;
};
int main() {
std::vector<int> nums = {5, 3, 1, 4, 1, 5, 9, 2, 6};
// Pass the container directly — no begin/end needed
std::ranges::sort(nums);
// nums: {1, 1, 2, 3, 4, 5, 5, 6, 9}
// find returns a borrowed iterator (safe with temporary ranges)
auto it = std::ranges::find(nums, 4);
if (it != nums.end()) {
std::cout << "Found: " << *it << '\n';
}
// count_if with ranges
auto even_count = std::ranges::count_if(nums, [](int x) { return x % 2 == 0; });
std::cout << "Even count: " << even_count << '\n'; // 3
// min/max on entire range
auto [min_it, max_it] = std::ranges::minmax_element(nums);
std::cout << "Min: " << *min_it << ", Max: " << *max_it << '\n';
// Sorting employees by salary (descending)
std::vector<Employee> team = {
{"Alice", 30, 95000},
{"Bob", 25, 72000},
{"Charlie", 35, 110000},
};
std::ranges::sort(team, std::ranges::greater{}, &Employee::salary);
// Charlie (110k), Alice (95k), Bob (72k)
for (const auto& e : team) {
std::cout << e.name << ": $" << e.salary << '\n';
}
}Projections — Transformer avant de comparer
Les projections sont une fonctionnalité unique des algorithmes basés sur les ranges. Elles vous permettent de spécifier comment « voir » chaque élément pour la comparaison ou le traitement, sans modifier l'élément lui-même.
#include <algorithm>
#include <ranges>
#include <vector>
#include <string>
#include <iostream>
#include <cctype>
int main() {
// Sort strings case-insensitively using a projection
std::vector<std::string> words = {"Banana", "apple", "Cherry", "date"};
std::ranges::sort(words, {}, [](const std::string& s) {
std::string lower = s;
std::transform(lower.begin(), lower.end(), lower.begin(),
[](char c) { return std::tolower(c); });
return lower;
});
// words: {"apple", "Banana", "Cherry", "date"}
// Original strings preserved, sorted by lowercase version
for (const auto& w : words) std::cout << w << ' ';
std::cout << '\n';
// Using member pointer as projection
struct Point { int x, y; };
std::vector<Point> points = {{3, 1}, {1, 5}, {2, 3}};
// Sort by x coordinate
std::ranges::sort(points, {}, &Point::x);
// points: {{1,5}, {2,3}, {3,1}}
// Find max by y coordinate
auto max_y = std::ranges::max_element(points, {}, &Point::y);
std::cout << "Max y: (" << max_y->x << ", " << max_y->y << ")\n"; // (1, 5)
// Binary search with projection
bool found = std::ranges::binary_search(points, 2, {}, &Point::x);
std::cout << std::boolalpha << "Has x=2: " << found << '\n'; // true
}L'opérateur pipe pour la composition
L'opérateur pipe | est la marque de fabrique des ranges C++20. Il vous permet de chaîner des adaptateurs de range (views) en un pipeline qui se lit de gauche à droite, comme un pipe Unix :
auto result = numbers
| std::views::filter([](int x) { return x % 2 == 0; })
| std::views::transform([](int x) { return x * x; })
| std::views::take(5);Cela se lit naturellement : « prendre les nombres, garder les pairs, les mettre au carré, prendre les 5 premiers. » Le pipeline est paresseux — aucun calcul n'a lieu tant que vous n'itérez pas sur result. Cela signifie qu'aucun vector intermédiaire n'est alloué.
Nous explorerons les views en profondeur dans la prochaine leçon.
Les algorithmes de range protègent contre les itérateurs pendants provenant de ranges temporaires. Si vous passez un conteneur temporaire, l'itérateur retourné serait pendant car le temporaire est détruit au point-virgule :
auto it = std::ranges::find(get_vector(), 42);
// Retourne std::ranges::dangling au lieu d'un itérateur !
// Tenter de le déréférencer ne compilera pas.C'est une fonctionnalité de sécurité à la compilation — le std::find traditionnel retournerait silencieusement un itérateur pendant, menant à un comportement indéfini à l'exécution. Le type sentinelle std::ranges::dangling transforme cela en erreur de compilation.
Préférez les algorithmes std::ranges:: dans le nouveau code — ils sont plus sûrs (pas d'itérateurs dépareillés, protection contre les pendants), plus lisibles (passez les conteneurs directement) et plus puissants (projections, concepts).
Utilisez les algorithmes traditionnels std:: lorsque :
- Votre base de code cible des compilateurs pré-C++20
- Vous avez besoin d'une surcharge spécifique que les ranges ne fournissent pas
- Vous travaillez avec des paires d'itérateurs provenant d'API héritées
Note : les algorithmes std::ranges:: ne sont pas des remplacements directs. Certains retournent des types différents (par ex., std::ranges::sort retourne l'itérateur de fin de la plage triée). Consultez la documentation lors de la migration.
- Les ranges C++20 vous permettent de passer les conteneurs directement aux algorithmes — plus besoin du boilerplate
begin()/end() - Les projections transforment les éléments pour la comparaison sans modifier les données originales
- Les concepts de range (
input_range,random_access_range, etc.) fournissent des contraintes claires à la compilation - L'opérateur pipe
|permet des pipelines de traitement de données composables et de gauche à droite std::ranges::danglingempêche les bugs d'itérateurs pendants à la compilation- Préférez les algorithmes
std::ranges::dans le nouveau code C++20 pour la sécurité, la lisibilité et la puissance
Quiz — Testez vos connaissances
(20 XP)1. Qu'est-ce qu'une projection dans le contexte des algorithmes de range C++20 ?
2. Que retourne `std::ranges::find` lorsqu'il est appelé avec un conteneur temporaire ?
3. Que fait `std::ranges::sort(vec, std::ranges::greater{}, &Employee::salary)` ?