Ranges: Overview

Ranges?

  • Range: abstraction of “something that contains elements”

  • Inevitably a range must have begin() and end() iterators ⟶ Range-based-for

  • Most standard algorithms come with a range variant

  • Less error prone

  • More comfortable

Containers And Views (Storage Behavior)

  • Containers are ranges that own their elements

  • Views do not own data

    • Refer to another range to draw elements from

    • Transformation done lazily during iteration/dereferencing

Examples

  • Dropping first two elements of a sequence, and printing the rest

  • no copy

#include <vector>
#include <ranges>
#include <iostream>

int main()
{
    std::vector numbers = {2, 1, 4, 3, 5}; 
    auto first_two_dropped = std::views::drop(numbers, 2);

    for (auto i: first_two_dropped)
        std::cout << i << std::endl;

    return 0;
}
  • Dropping first two, taking next two

#include <vector>
#include <ranges>
#include <iostream>

int main()
{
    std::vector numbers = {2, 1, 4, 3, 5}; 
    auto first_two_dropped = std::views::drop(numbers, 2);
    auto first_two_dropped_next_two_taken = std::views::take(first_two_dropped, 2);

    for (auto i: first_two_dropped_next_two_taken)
        std::cout << i << std::endl;

    return 0;
}

Pipe Syntax

  • Syntactic sugar

  • Extremely readable

  • Example: dropping first two, taking next two

#include <vector>
#include <ranges>
#include <iostream>

int main()
{
    std::vector numbers = {2, 1, 4, 3, 5}; 

    for (auto i: numbers | std::views::drop(2) | std::views::take(2))
        std::cout << i << std::endl;

    return 0;
}

Views As Parameters: Good Old Template

  • As an old-style template

    #include <vector>
    #include <iostream>
    
    template <typename R>                                  // <--- good ol'
    void print(const R& range)                             //      function template
    {
        for (const auto& elem: range)
            std::cout << elem << std::endl;
    }
    
    int main()
    {
        std::vector numbers = { 1,2,3,4,5 };
        print(numbers);
        return 0;
    }
    

Views As Parameters: Abbreviated Function Template

This is new in C++20. The feature is inspired by generic lambda (new in C++14, and refined from then on; see here).

#include <vector>
#include <iostream>

void print(const auto& range)                          // <--- cool!
{
    for (const auto& elem: range)
        std::cout << elem << std::endl;
}

int main()
{
    std::vector numbers = { 1,2,3,4,5 };
    print(numbers);
    return 0;
}

Views As Parameters: Concepts

  • Abbreviated function templates are best combined with concepts

  • ⟶ Better compiler errors

#include <vector>
#include <iostream>

void print(const std::ranges::input_range auto& range) // <--- not cool, but safe
{
    for (const auto& elem: range)
        std::cout << elem << std::endl;
}

int main()
{
    std::vector numbers = { 1,2,3,4,5 };
    print(numbers);
    return 0;
}

Available <ranges> Concepts

Concept name

Description

std::ranges::input_range

Can be iterated from beginning to end at least once

std::ranges::forward_range

Can be iterated from beginning to end multiple times

std::ranges::bidirectional_range

Iterator can also move backwards with operator--()

std::ranges::random_access_range

You can jump to elements in constant-time with operator[]()

std::ranges::contiguous_range

Elements are always stored consecutively in memory