نطاقات C++20: نموذج جديد
اكتشف نطاقات C++20 — إعادة تصميم حديثة لواجهة خوارزمية STL. تعرف على مفاهيم النطاق والخوارزميات المستندة إلى النطاق والإسقاطات ومشغل الأنابيب لتحويلات البيانات القابلة للتركيب.
لماذا النطاقات؟
تعد نطاقات C++20 بمثابة ترقية أساسية لنموذج خوارزمية STL. تتطلب STL التقليدية تمرير أزواج التكرارات إلى كل خوارزمية، وهو أمر مطول وعرضة للخطأ (يمكنك تمرير التكرارات عن طريق الخطأ من حاويات مختلفة). تعمل النطاقات على حل هذه المشكلة من خلال السماح لك بتمرير الحاوية نفسها — أو أي كائن يحددbegin()وend().
التحسينات الرئيسية على الخوارزميات التقليدية:
1. تمرير الحاويات مباشرةً —std::ranges::sort(vec)بدلاً منstd::sort(vec.begin(), vec.end())
2. الإسقاطات — تحويل العناصر قبل المقارنة، دون تعديلها
3. المفاهيم — للخوارزميات متطلبات واضحة لوقت الترجمة
4. القابلية للتركيب — عمليات السلسلة مع مشغل الأنابيب|
5. التقييم البطيء — يتم عرض العناصر الحسابية عند الطلب، مع تجنب النسخ الوسيطة
Range Concepts
يحدد 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 — يمكن تحويله إلى طريقة عرض
تُستخدم هذه المفاهيم لتقييد الخوارزميات المستندة إلى النطاق، مما يوفر لك رسائل خطأ واضحة عند تمرير نطاق غير متوافق.
Range-Based Algorithms
Range-based algorithms live instd::ranges::and accept entire containers. They also return richer result types instead of bare iterators.
#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 — Transform Before Comparing
Projections are a unique feature of range-based algorithms. They let you specify how to "view" each element for comparison or processing, without modifying the element itself.
#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
}The Pipe Operator for Composition
The pipe operator|is the hallmark of C++20 ranges. It lets you chain range adaptors (views) into a pipeline that reads left-to-right, like a Unix shell pipe:
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 result. This means no intermediate vectors are allocated.
We will explore views in depth in the next lesson.
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 ranges let you pass containers directly to algorithms — no more
begin()/end()boilerplate - 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::danglingprevents dangling iterator bugs at compile time- Prefer
std::ranges::algorithms in new C++20 code for safety, readability, and power
اختبار — اختبر معرفتك
(20 نقطة خبرة)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)`؟