May 26th, 2021

C++20 Ranges are complete in Visual Studio 2019 version 16.10

Casey Carter
Senior Template Librarian

We are proud to announce completion of our implementation of C++20 Ranges in the Standard Library in the VS2019 v16.10 release under/std:c++latest. We announced the first useful user-visible parts of Ranges in VS 2019 v16.6 in mid 2020, the trickle accelerated into a gushing stream, and the final parts are now in place. This represents a huge body of work with input from multiple open-source contributors over the last two years.

Ironically enough, ranges are nothing new to the Standard Library. STL algorithms have always operated on ranges of elements whose bounds are denoted by an iterator that denotes the first element and an iterator pointing past the end of the sequence of elements subject to a computation. The novelty in C++20 Ranges is that we recognize the expressive power that comes from passing ranges around directly as abstractions instead of passing iterator pairs – eliminating passing iterators into different ranges as a source of errors – and that operations on ranges can compose more easily than operations on their elements.

What components comprise the C++20 Ranges design?

Ranges first relaxes the old C++ iterator-pair design by allowing the sentinel that denotes the end of a sequence of elements to have a different type than the iterator that denotes the beginning. This enables expression of simple notions like a range bounded by a pointer-to-character and a delimiter sentinel that compares equal to a pointer-to-character when the pointer points to '\0'. A Range is then any expression meow such that std::ranges::begin(meow) and std::ranges::end(meow) return an iterator and a sentinel.

The Ranges library expresses predicates over types (“is this a bidirectional iterator?”) and relationships between types (“is this a valid iterator-and-sentinel pair?”) using C++ Concepts. Ranges is the first use – and quite an extensive use, at that – of the new Concepts language feature in the C++ Standard Library. Concepts are nicely expressive in that they allow the specification of type requirements, and to a lesser extent preconditions on the values of arguments, to appear directly in code as syntax instead of appearing in documentation as English prose. (See “C++20 Concepts Are Here in Visual Studio 2019 version 16.3” for more discussion of C++ Concepts.)

Ranges adds a set of algorithms – mirrors of the algorithms defined in namespace std – in namespace std::ranges. These algorithms are constrained with Concepts, and unlike their siblings in std accept both range arguments, and iterator-sentinel pair arguments where the sentinel and iterator types differ.

Ranges also unsurprisingly adds a slew of ranges to the Standard Library. The Standard splits these out into factories that create ranges:

  • std::views::iota(0, 42) is the range of integers from 0 up to but not including 42,
  • std::views::istream_view<int>(std::cin) is a range of whitespace-delimited integers read from std::cin,

and adaptors that transform the elements of an underlying range into a new range:

  • std::views::filter(my_vec, [](const auto& x) { return x % 2 == 0; }) is a range of only the even elements of my_vec,
  • std::views::transform(my_vec, [](const auto& x) { return 3 * x; }) is a range of elements with value 3k where k is the value of the corresponding element of my_vec.

The range adaptors are often best thought of as lazy, composable algorithms since they do no work until you begin to iterate over them and they are functions from ranges to ranges. We could compose the above two examples into:

auto r1 = std::views::filter(my_vec, [](const auto& x) { return x % 2 == 0; });
auto r2 = std::views::transform(r1, [](const auto& x) { return 3 * x; });

for example, to get a range consisting of elements whose value is three times the value of the even elements of my_vec. The design even provides a pipeline syntax to facilitate this composition, using | to provide a left-hand range as input to a right-hand range adaptor:

auto r = my_vec
       | std::views::filter([](const auto& x) { return x % 2 == 0; })
       | std::views::transform([](const auto& x) { return 3 * x; });

Doing the equivalent work with algorithms would require storing intermediate and final results in some kind of container, which an adaptor composition like this avoids by working element-at-a-time.

Where did this implementation come from?

A typical STL feature is described by a 10-20 page proposal that WG21 polishes into a specification to merge into the C++ Standard. P0896R4 “The One Ranges Proposal” was around 230 pages. As if that weren’t enough, our tracking issue for Ranges implementation documents a litany of 22 follow-up proposals (design changes) and 35 LWG issues (bug fixes) that are covered in the Ranges implementation. Our implementation plan broke the work down into bitesize pieces starting with “Implement common_reference and common_type change” from May 13 2019, one of four internal PRs from before the STL became open-source, through another 99 GitHub pull requests ending in “Define __cpp_lib_ranges [feature-test macro]” on Mar 18 2021. While we certainly drew from experience prototyping the library for the Ranges Technical Specification to provide guidance, the STL implementation was almost entirely a full rewrite reusing only some test cases.

This is certainly the largest single Standard Library feature to-date, and we can’t express enough how much we appreciate all the work from the many members of the C++ community that helped bring it to life. We’d like to personally thank AdamBucior, ahanamuk, barcharcraz, BillyONeal, bkentel, CaseyCarter, cbezault, cpplearner, fsb4000, futuarmo, MahmoudGSaleh, miscco, mnatsuhara, statementreply, StephanTLavavej, SuperWig, and timsong-cpp, each of whom contributed either code, code review comments, or issue reports for MSVC Ranges. On a personal note, my Ranges journey started six years ago this month at the WG21 meeting in Lenexa, Kansas. As an unemployed enthusiast C++ aficionado very excited about the design in Eric Niebler’s N4128 “Ranges for the Standard Library,” I never would have believed that I’d spend the next six years working on that design through the standardization process right up through today’s shipping product.

Can I try it today?

Yes, that’s the point! Please give the available components a try, and we’ll keep them coming. Don’t forget to download (or update to) Visual Studio 2019 version 16.10; some but not all of the Ranges design is present in earlier releases. Be aware that WG21 is processing some breaking changes for C++20, three of which have bearing directly on Ranges. You can expect changes to (1) std::ranges::split_view / std::views::split, which WG21 has largely redesigned since publishing C++20, (2) std::ranges::join_view / std::views::join, which has been tweaked to be usable in more cases, and (3) the std::ranges::view concept itself will lose its requirement for default construction allowing range adaptors to more efficiently adapt underlying ranges that aren’t default constructible by producing a result view that is not default constructible.

We love to hear your feedback and suggestions. We can be reached via the comments below, Developer Community, and Twitter (@VisualC). For issues and questions specific to the STL or our Ranges implementation, the best way to reach is by filing an issue on GitHub.

 

 

 

 

 

 

 

 

 

 

 

 

 

Category
C++

Author

Casey Carter
Senior Template Librarian

1 comment

Discussion is closed. Login to edit/delete existing comments.

  • Hristo Hristov

    We really need examples and benchmarks.