Sari la conținut

Gama C++20: O nouă paradigmă

Descoperiți gamele C++20 — o reproiectare modernă a interfeței algoritmului STL. Învățați concepte de gamă, algoritmi bazați pe gamă, proiecții și operatorul conductei pentru transformările de date componabile.

De ce Ranges?

Gamele C++20 sunt o actualizare fundamentală a modelului de algoritm al STL. STL tradițional necesită trecerea perechilor de iteratoare la fiecare algoritm, care este pronunțat și predispus la erori (puteți trece accidental iteratoare din diferite containere). Intervalele rezolvă acest lucru permițându-vă să treceți containerul în sine — sau orice obiect care defineștebegin()șiend().

Îmbunătățiri cheie față de algoritmii tradiționali:

1. Trimite containerele direct —std::ranges::sort(vec)în loc destd::sort(vec.begin(), vec.end())
2. Proiecții — transformă elemente înainte de a compara, fără a le modifica
3. Concepte — algoritmii au cerințe clare de compilare
4. Composabilitate — operațiuni în lanț cu operatorul de conducte|
5. Evaluare leneșă — vizualizează elemente de calcul la cerere, evitând copiile intermediare

Concepte de gamă

C++20 definește o ierarhie a conceptelor de gamă în:

- std::ranges::range — arebegin()șiend()
- std::ranges::sized_range — aresize()în O(1)
- std::ranges::input_range — poate fi citit o dată (ca iteratoarele de intrare)
- std::ranges::forward_range — traversare înainte cu mai multe treceri
- std::ranges::bidirectional_range — înainte + înapoi
- std::ranges::random_access_range — salturi bidirecționale + O(1).
- std::ranges::contiguous_range — acces aleatoriu + memorie contiguă
- std::ranges::common_range —begin()șiend()returnează același tip
- std::ranges::viewable_range — poate fi convertit într-o vizualizare

Aceste concepte sunt folosite pentru a constrânge algoritmii bazați pe intervale, oferindu-vă mesaje de eroare clare atunci când treceți un interval incompatibil.

Algoritmi bazați pe intervale

Algoritmii bazați pe intervale trăiesc înstd::ranges::și acceptă containere întregi. De asemenea, returnează tipuri de rezultate mai bogate în loc de iteratoare simple.

range_algorithms.cpp
#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';
    }
}

Proiecții — Transformați înainte de comparare

Proiecțiile sunt o caracteristică unică a algoritmilor bazați pe interval. Acestea vă permit să specificați cum să „vizualizați” fiecare element pentru comparare sau procesare, fără a modifica elementul în sine.

projections.cpp
#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
}

Operatorul de conducte pentru compoziție

Operatorul de conducte|este semnul distinctiv al gamei C++20. Vă permite să conectați adaptoarele de gamă (vizualizări) într-o conductă care citește de la stânga la dreapta, ca o conductă shell 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 result. Aceasta înseamnă că nu sunt alocați vectori intermediari.

Vom explora punctele de vedere în profunzime în lecția următoare.

Capcană

Algoritmii de interval protejează împotriva iteratorilor suspendați din intervalele temporare. Dacă treceți un container temporar, iteratorul returnat ar atârna deoarece temporarul este distrus la punctul și virgulă:

Tipul sentinel ``cpp
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 face ca aceasta să fie o eroare de compilare.

Bună practică

Preferați algoritmiistd::ranges::în codul nou - sunt mai siguri (fără iteratoare nepotrivite, protecție suspendată), mai lizibili (trece containere direct) și mai puternice (proiecții, concepte).

Utilizați algoritmi tradiționalistd::atunci când:
- Baza de cod vizează compilatoarele pre-C++20
- Aveți nevoie de o supraîncărcare specifică pe care intervalele nu o oferă
- Lucrezi cu perechi de iteratoare din API-uri vechi

Notă: algoritmiistd::ranges::sunt nu înlocuitori introduși. Unele returnează tipuri diferite (de exemplu,std::ranges::sortreturnează iteratorul final al intervalului sortat). Verificați documentația la migrare.

Idei cheie
  • Gamele C++20 vă permit să transferați containerele direct la algoritmi - nu mai suntbegin()/end()boilerplate
  • Proiecțiile transformă elemente pentru comparare fără a modifica datele originale
  • Conceptele de interval (input_range,random_access_rangeetc.) oferă constrângeri clare de timp de compilare
  • Operatorul de conducte|permite conducte componabile de procesare a datelor de la stânga la dreapta
  • std::ranges::danglingprevine erorile iteratoarelor suspendate în timpul compilării
  • Preferați algoritmiistd::ranges::în noul cod C++20 pentru siguranță, lizibilitate și putere

Test — Verifică-ți cunoștințele

(20 XP)

1. Ce este o proiecție în contextul algoritmilor din gama C++20?

2. Ce returnează`std::ranges::find`atunci când este apelat cu un container temporar?

3. Ce face`std::ranges::sort(vec, std::ranges::greater{}, &Employee::salary)`?