Přeskočit na obsah

Rozsahy C++20: Nové paradigma

Objevte řady C++20 — moderní redesign rozhraní algoritmu STL. Naučte se koncepty rozsahů, algoritmy založené na rozsahu, projekce a operátor potrubí pro transformace složených dat.

Proč Ranges?

C++20 Ranges are a fundamental upgrade to the STL's algorithm model. The traditional STL requires passing iterator pairs to every algorithm, which is verbose and error-prone (you can accidentally pass iterators from different containers). Ranges solve this by allowing you to pass the container itself — or any object that definesbegin()andend().

Key improvements over traditional algorithms:

1. Pass containers directly —std::ranges::sort(vec)instead ofstd::sort(vec.begin(), vec.end())
2. Projections — transform elements before comparing, without modifying them
3. Concepts — algorithms have clear compile-time requirements
4. Composability — chain operations with the pipe operator|
5. Lazy evaluation — views compute elements on demand, avoiding intermediate copies

Koncepty rozsahu

C++20 definuje hierarchii konceptů rozsahu v:

- std::ranges::range – mábegin()aend()
- std::ranges::sized_range – másize()v O(1)
- std::ranges::input_range — lze přečíst jednou (jako vstupní iterátory)
- std::ranges::forward_range — víceprůchodový posuv vpřed
- std::ranges::bidirectional_range — vpřed + vzad
- std::ranges::random_access_range — obousměrné + O(1) skoky
- std::ranges::contiguous_range — náhodný přístup + souvislá paměť
– std::ranges::common_range –begin()aend()vrátí stejný typ
- std::ranges::viewable_range — lze převést na pohled

Tyto koncepty se používají k omezení algoritmů založených na rozsahu, což vám poskytne jasné chybové zprávy, když projdete nekompatibilním rozsahem.

Algoritmy založené na rozsahu

Algoritmy založené na rozsahu fungují vstd::ranges::a přijímají celé kontejnery. Místo holých iterátorů také vracejí bohatší typy výsledků.

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';
    }
}

Projekce — Transformujte před porovnáním

Projekce jsou jedinečnou vlastností algoritmů založených na rozsahu. Umožňují vám určit, jak „zobrazit“ každý prvek pro porovnání nebo zpracování, aniž byste museli prvek samotný upravovat.

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
}

Operátor potrubí pro kompozici

Potrubní operátor|je charakteristickým znakem řad C++20. Umožňuje vám zřetězit adaptéry rozsahu (pohledy) do potrubí, které se čte zleva doprava, jako unixové shell potrubí:

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 výsledek. To znamená, že nejsou přiděleny žádné mezilehlé vektory.

Pohledy do hloubky prozkoumáme v další lekci.

Úskalí

Algoritmy rozsahu chrání před visícími iterátory z dočasných rozsahů. Pokud minete dočasný kontejner, vrácený iterátor bude viset, protože dočasný je zničen u středníku:

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::rozsahy::dangling typ sentinelu způsobuje chybu kompilace.

Osvědčený postup

Preferujte v novém kódu algoritmystd::ranges::– jsou bezpečnější (žádné neodpovídající iterátory, visící ochrana), čitelnější (předávají kontejnery přímo) a výkonnější (projekce, koncepty).

Používejte tradiční algoritmystd::, když:
- Vaše kódová základna se zaměřuje na kompilátory starší než C++20
- Potřebujete specifické přetížení, které rozsahy neposkytují
- Pracujete s dvojicemi iterátorů ze starších rozhraní API

Poznámka: Algoritmystd::ranges::jsou ne náhrady typu drop-in. Některé vrátí různé typy (např.std::ranges::sortvrátí koncový iterátor seřazeného rozsahu). Při migraci zkontrolujte dokumentaci.

Klíčové poznatky
  • Řady C++20 vám umožňují předat kontejnery přímo algoritmům – už žádné standardníbegin()/end()
  • Projekce transformují prvky pro porovnání bez úpravy původních dat
  • Koncepty rozsahů (input_range,random_access_rangeatd.) poskytují jasná omezení v době kompilace
  • Operátor potrubí|umožňuje skládatelná potrubí zpracování dat zleva doprava
  • std::ranges::danglingzabraňuje visícím chybám iterátoru v době kompilace
  • Preferujte algoritmystd::ranges::v novém kódu C++20 pro bezpečnost, čitelnost a výkon

Kvíz — Otestujte své znalosti

(20 XP)

1. Co je projekce v kontextu algoritmů rozsahu C++20?

2. Co vrátí`std::ranges::find`při volání s dočasným kontejnerem?

3. Co dělá`std::ranges::sort(vec, std::ranges::greater{}, &Employee::salary)`?