C++20 Ranges: A New Paradigm
Ανακαλύψτε τις σειρές C++20 — ένας σύγχρονος επανασχεδιασμός της διεπαφής αλγορίθμου STL. Μάθετε έννοιες εύρους, αλγόριθμους που βασίζονται σε εύρος, προβολές και τον χειριστή σωλήνων για μετασχηματισμούς δεδομένων με δυνατότητα σύνθεσης.
Γιατί Ranges;
Τα εύρη C++20 είναι μια θεμελιώδης αναβάθμιση στο μοντέλο αλγορίθμου του STL. Το παραδοσιακό STL απαιτεί τη μετάδοση ζευγών επαναλήπτη σε κάθε αλγόριθμο, ο οποίος είναι περιεκτικός και επιρρεπής σε σφάλματα (μπορείτε να περάσετε κατά λάθος επαναλήπτες από διαφορετικά κοντέινερ). Τα εύρη λύνουν αυτό επιτρέποντάς σας να περάσετε το ίδιο το κοντέινερ — ή οποιοδήποτε αντικείμενο που ορίζει ταbegin()καιend().
Βασικές βελτιώσεις σε σχέση με τους παραδοσιακούς αλγόριθμους:
1. Περάστε απευθείας τα κοντέινερ —std::ranges::sort(vec)αντί γιαstd::sort(vec.begin(), vec.end())
2. Προβολές — μετασχηματίστε στοιχεία πριν από τη σύγκριση, χωρίς να τα τροποποιήσετε
3. Έννοιες — οι αλγόριθμοι έχουν σαφείς απαιτήσεις χρόνου μεταγλώττισης
4. Συνθεσιμότητα — λειτουργίες αλυσίδας με τον χειριστή σωλήνων|
5. Τεμπέλης αξιολόγηση — προβάλλει υπολογιστικά στοιχεία κατ' απαίτηση, αποφεύγοντας τα ενδιάμεσα αντίγραφα
Έννοιες εύρους
Το C++20 ορίζει μια ιεραρχία εννοιών εύρους στο:
- std::ranges::range — έχειbegin()καιend()
- std::ranges::sized_range — έχειsize()σε O(1)
- std::ranges::input_range — μπορεί να διαβαστεί μία φορά (όπως επαναλήψεις εισόδου)
- std::ranges::forward_range — διέλευση εμπρός πολλαπλών περασμάτων
- std::ranges::bidirectional_range — εμπρός + πίσω
- std::ranges::random_access_range — αμφίδρομα + O(1) άλματα
- std::ranges::contiguous_range — τυχαία πρόσβαση + συνεχόμενη μνήμη
- std::ranges::common_range —begin()καιend()επιστρέφουν τον ίδιο τύπο
- std::ranges::viewable_range — μπορεί να μετατραπεί σε προβολή
Αυτές οι έννοιες χρησιμοποιούνται για τον περιορισμό αλγορίθμων που βασίζονται σε εύρος, δίνοντάς σας σαφή μηνύματα σφάλματος όταν περνάτε ένα μη συμβατό εύρος.
Αλγόριθμοι βάσει εύρους
Οι αλγόριθμοι βάσει εύρους ζουν στοstd::ranges::και δέχονται ολόκληρα κοντέινερ. Επιστρέφουν επίσης πλουσιότερους τύπους αποτελεσμάτων αντί για γυμνούς επαναληπτικούς.
#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';
}
}Προβολές — Μετασχηματισμός πριν από τη σύγκριση
Οι προβολές είναι ένα μοναδικό χαρακτηριστικό των αλγορίθμων που βασίζονται σε εύρος. Σας επιτρέπουν να καθορίσετε πώς να "προβάλετε" κάθε στοιχείο για σύγκριση ή επεξεργασία, χωρίς να τροποποιήσετε το ίδιο το στοιχείο.
#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
}Ο χειριστής σωλήνων για τη σύνθεση
Ο χειριστής σωλήνων|είναι το σήμα κατατεθέν των σειρών C++20. Σας επιτρέπει να συνδέσετε τους προσαρμογείς εύρους (προβολές) σε μια σωλήνωση που διαβάζει από αριστερά προς τα δεξιά, όπως ένας σωλήνας κελύφους 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);This reads naturally: "take numbers, keep the even ones, square them, take the first 5." The pipeline is lazy — no computation happens until you iterate over αποτέλεσμα. Αυτό σημαίνει ότι δεν κατανέμονται ενδιάμεσα διανύσματα.
Θα εξερευνήσουμε τις απόψεις σε βάθος στο επόμενο μάθημα.
Range algorithms protect against dangling iterators from temporary ranges. If you pass a temporary container, the returned iterator would dangle because the temporary is destroyed at the semicolon:
auto it = std::ranges::find(get_vector(), 42);
// Returns std::ranges::dangling instead of an iterator!
// Attempting to dereference it won't compile.This is a compile-time safety feature — traditional std::find would silently return a dangling iterator, leading to undefined behavior at runtime. The std::ranges::dangling sentinel type makes this a compile error.
Preferstd::ranges::algorithms in new code — they are safer (no mismatched iterators, dangling protection), more readable (pass containers directly), and more powerful (projections, concepts).
Use traditionalstd::algorithms when:
- Your codebase targets pre-C++20 compilers
- You need a specific overload that ranges don't provide
- You work with iterator pairs from legacy APIs
Note:std::ranges::algorithms are not drop-in replacements. Some return different types (e.g.,std::ranges::sortreturns the end iterator of the sorted range). Check the documentation when migrating.
- Οι σειρές C++20 σάς επιτρέπουν να περνάτε κοντέινερ απευθείας σε αλγόριθμους — όχι άλλο λέβητα
begin()/end() - Projections transform elements for comparison without modifying the original data
- Range concepts (
input_range,random_access_range, etc.) provide clear compile-time constraints - The pipe operator
|enables composable, left-to-right data processing pipelines - Το
std::ranges::danglingαποτρέπει τα κρεμαστά σφάλματα επαναλήπτη κατά το χρόνο μεταγλώττισης - Prefer
std::ranges::algorithms in new C++20 code for safety, readability, and power
Κουίζ — Δοκιμάστε τις γνώσεις σας
(20 XP)1. What is a projection in the context of C++20 range algorithms?
2. What does`std::ranges::find`return when called with a temporary container?
3. Τι κάνει το`std::ranges::sort(vec, std::ranges::greater{}, &Employee::salary)`;