C++20: The Ranges Library

Thanks to the ranges library in C++20, working with the Standard Template Library (STL) will become much more comfortable and powerful. The algorithms of the ranges library are lazy, can work directly on the container and can easily be composed. To make it short: The range library’s comfort and power are due to its functional ideas. Let me show you what that means.

Before I dive into the details, here is a first example of the ranges library:

// rangesFilterTransform.cpp

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

int main() {

    std::vector<int> numbers = {1, 2, 3, 4, 5, 6};
  
    auto results = numbers | std::views::filter([](int n){ return n % 2 == 0; })
                           | std::views::transform([](int n){ return n * 2; });
                           
    for (auto v: results) std::cout << v << " ";     // 4 8 12

}

You have to read the expression from left to right. The pipe symbol stands for function composition: First, all numbers can pass which are even (std::views::filter([](int n){ return n % 2 == 0; })). Afterward, each remaining number is mapped to its double (std::views::transform([](int n){ return n * 2; })). The small example already shows two new ranges library features: Function composition, which operators on the entire container.

Now you should be prepared for the details. Let’s go back to square one: ranges and views are concepts.

Range

  • std::range: A range is a group of items you can iterator over. It provides a begin iterator and an end sentinel. Of course, the containers of the STL are ranges.

There exist refinements of std::range:

  • std::ranges::input_range: specifies a range whose iterator type satisfies input_iterator (can iterate from beginning to end at least once)
  • std::ranges::output_range: specifies a range whose iterator type satisfies output_iterator
  • std::ranges::forward_range: specifies a range whose iterator type satisfies forward_iterator (can iterate from beginning to end more than once)
  • std::ranges::bidirectional_range: specifies a range whose iterator type satisfies bidirectional_iterator (can iterate forward and backward more than once)
  • std::ranges::random_access_range: specifies a range whose iterator type satisfies random_access_iterator (can jump in constant time to an arbitrary element with the index operator [])
  • std::ranges::contiguous_range: specifies a range whose iterator type satisfies contiguous_iterator (elements are stored consecutively in memory)

The containers of the STL and std::string model different concepts:

RangeConcepts

 

Rainer D 6 P2 500x500Modernes C++ Mentoring

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (open)
  • "Generic Programming (Templates) with C++": October 2024
  • "Embedded Programming with Modern C++": October 2024
  • "Clean Code: Best Practices for Modern C++": March 2025
  • Do you want to stay informed: Subscribe.

     

    A container supporting the std::ranges::contiguous_range concept supports all other concepts above, such as std::ranges::random_access_range, std::ranges::bidirectional_range, and std::ranges::input_range. The same observation holds for all other ranges.

    View

    • You apply a View on a range and perform some operations. A view does not own data, and it’s time to copy, move, assignment its constant. Here is a quote from Eric Niebler’s range-v3 implementation, which is the base for the C++20 ranges: “Views are composable adaptations of ranges where the adaptation happens lazily as the view is iterated.

    std::vector<int> numbers = {1, 2, 3, 4, 5, 6};
    
    auto results = numbers | std::views::filter([](int n){ return n % 2 == 0; })
                           | std::views::transform([](int n){ return n * 2; });
    

    In this code snippet, numbers are the range, and std::views::filter and std::views::transform are the views.

    Due to the power of views, C++20 allows programming in a functional style. Views can be combined and are lazy. l already presented two views, but we get more.

    std::all_view, std::views::all               // takes all elements
    
    std::ref_view                                // takes all elements of another view
    
    std::filter_view, std::views::filter         // takes the elements which satisfies the predicate
    
    std::transform_view, std::views::transform   // transforms each element
    
    std::take_view, std::views::take             // takes the first N elements of another view
    
    std::take_while_view, std::views::take_while // takes the elements of another view as long as the predicate returns true
    
    std::drop_view, std::views::drop             // skips the first N elements of another view
    
    std::drop_while_view, std::views::drop_while // skips the initial elements of another view until the predicate returns false
    
    std::join_view, std::views::join             // joins a view of ranges
    
    std::split_view, std::views::split           // splits a view by using a delimiter
    
    std::common_view, std::views::common         // converts a view into a std::common_range
    
    std::reverse_view, std::views::reverse       // iterates in reverse order
    
    std::basic_istream_view, std::istream_view   // applies operator>> on the view
    
    std::elements_view, std::views::elements     // creates a view on the N-th element of tuples
    
    std::keys_view, std::views::keys             // creates a view on the first element of a pair-like values
    
    std::values_view, std::views::values         // creates a view on the second elements of a pair-like values
    

    You can generally use a view such as std::views::transform with the alternative name std::transform_view.  I show the usage of various views as I go on.

    Implementation Status

    As far as I know, there is no implementation of the ranges library now (February 2020) available. This is not an issue. You can use the mentioned  range-v3 implementation on the online Wandbox or the Compiler Explorer with the HEAD GCC. Here is what you have to do to translate my examples, such as rangesFilterTransform.cpp to see it in action.

    • Replace the namespaces std::views:: with ranges::views::.
    • Replace the header <ranges> with the header <range/v3/all.hpp>. For more details, study the documentation in range-v3 implementation.
    • Compile your program with C++20 support: -std=c++2a.
    • When you use Compiler Explorer, you have to use the trunk version of the range-v3 implementation. The following picture should help you find the option.

    CompilerExplorer

    Transforming the program get rangesFilterTransform.cpp gives me the following program.

    // rangesV3FilterTransform.cpp
    
    #include <iostream>
    #include <range/v3/all.hpp>
    #include <vector>
    
    int main() {
    
        std::vector<int> numbers = {1, 2, 3, 4, 5, 6};
      
        auto results = numbers | ranges::views::filter([](int n){ return n % 2 == 0; })
                               | ranges::views::transform([](int n){ return n * 2; });
                               
        for (auto v: results) std::cout << v << " ";    
    
    }
    

    Thanks to Wandbox, here is the output of the program without a faked source file:Wandbox

    I will use in my future posts the proposed C++20 syntax. Consequentially, you have to do the transformation step manually.

    What’s next?

    In this post, I explained the basics of the ranges library. These basics enable me to write in my next post about their power. The ranges library extends C++20 with two new concepts: function composition and lazy evaluation. This is the reason ranges belong to the big four of C++20. Each part of the big four changes how we think and write modern C++.

    Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Stephen Kelley, Kyle Dean, Tusar Palauri, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery,and Matt Godbolt.

    Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.

    My special thanks to Embarcadero
    My special thanks to PVS-Studio
    My special thanks to Tipi.build 
    My special thanks to Take Up Code
    My special thanks to SHAVEDYAKS

    Modernes C++ GmbH

    Modernes C++ Mentoring (English)

    Do you want to stay informed about my mentoring programs? Subscribe Here

    Rainer Grimm
    Yalovastraße 20
    72108 Rottenburg

    Mobil: +49 176 5506 5086
    Mail: schulung@ModernesCpp.de
    Mentoring: www.ModernesCpp.org

    Modernes C++ Mentoring,

     

     

    0 replies

    Leave a Reply

    Want to join the discussion?
    Feel free to contribute!

    Leave a Reply

    Your email address will not be published. Required fields are marked *