November 13, 2018·6 min read·engineeringcpprangesconceptsalgorithms

Ranges adoption gives algorithms a shared vocabulary of requirements

San Diego adopts ranges for the C++20 draft. The proposal connects constrained algorithms, projections and views while preserving existing library interfaces alongside the new facilities.

A generic algorithm needs more than a pair of expressions that happen to compile. It needs a contract for traversal, element access and the operations it performs on those elements. San Diego's adoption of ranges puts a shared vocabulary for those contracts into the C++20 working draft. Herb Sutter's November 13 meeting report confirms the adoption at the meeting that ended on November 10.

The pre-meeting P0896R3 proposal, dated October 7, explains the design and the reasons for merging it. The authors are Eric Niebler, Casey Carter and Christopher Di Bella. Their goal is to deliver constrained generic library components alongside the language support needed to express their requirements.

I value C++ templates for abstractions that can avoid runtime overhead, but the cost in diagnostics remains significant. A common definition of an iterator or range can help make the source-level requirement explicit. That benefit depends on the actual concept contract and its semantic obligations, rather than on the mere presence of shorter algorithm calls.

A range groups traversal without erasing its capabilities

The proposal defines a range through its beginning and end operations. It does not require those operations to return the same type. An iterator identifies the current element, while a sentinel can describe termination through a different representation. A common range is the more specific case in which both endpoint types agree.

That distinction lets an algorithm state only the endpoint relationship it needs. Requiring two identical iterator types would exclude designs whose stopping condition naturally has a different representation. The proposal retains iterator-and-sentinel overloads as well as overloads that take a range object, so grouping endpoints does not remove their underlying role.

Capabilities remain explicit. Input, forward and bidirectional ranges describe progressively stronger traversal properties. Random-access and contiguous ranges add further requirements. The names therefore do not make every range interchangeable with every algorithm. Sorting needs capabilities that a single-pass input sequence does not provide.

The sized-range concept adds a separate requirement. P0896R3 says that obtaining its size has constant complexity and agrees with the distance of the range. Having a member that returns a number is not enough if computing it actually traverses every element. The concept includes a semantic and complexity promise in addition to the expression's syntax.

A projection separates selection of a value from comparison

The proposal defines a projection as the transformation an algorithm applies before inspecting an element's value. Sorting records by one member is a direct use. The comparison can remain an ordinary less-than relation while the projection identifies the field being compared.

The pre-meeting proposal's interface permits this shape:

// Interface illustrated by the pre-meeting ranges proposal.
#include <algorithm>
#include <string_view>

struct item {
    int priority;
    std::string_view name;
};

void order(item (&items)[3])
{
    std::ranges::sort(items, std::ranges::less<>{},
                      [](const item& value) {
                          return value.priority;
                      });
}

This example uses the proposal's comparison-object spelling and does not claim that an ordinary C++17 library supplies it. The objects being rearranged are still complete item values. The projection changes what the algorithm compares, not which portion of each record moves when sorting the sequence.

The distinction is useful because a comparator that embeds member selection has two responsibilities. A projection gives that selection its own argument and lets the algorithm's constraints describe the resulting relationship. The default identity projection preserves the ordinary case where the algorithm operates directly on each element.

Projection also has a cost model. The proposal states sorting complexity in terms of comparisons and applications of the projection. A projection need not be free simply because its syntax is concise. A caller that performs an expensive computation there is choosing work that the algorithm may repeat according to its specified bounds.

Views describe traversal with inexpensive copying

P0896R3 distinguishes a view from an arbitrary owning container. Its view requirement includes constant-time copy, move and assignment operations. The cost does not grow with the number of elements represented. Copying an ordinary container's complete contents therefore does not fit that view contract.

The proposal's examples include an iterator pair, shared ownership of elements and a range that generates values on demand. A view is consequently not defined solely by absence of ownership. What matters here is its range behavior and the cost and semantics of copying its representation.

Filter and transform views make the computation visible during traversal. A filter presents elements satisfying a predicate. A transform presents the result of applying a callable to each element. Constructing a view does not mean that every transformed value has already been stored in a new container.

The filter implementation wording gives a concrete example. Its beginning operation locates the first matching element and caches the result to meet the range complexity requirement. Incrementing its iterator searches for the next match. The work belongs to finding traversal positions, not to eagerly constructing a separate filtered array.

That mechanism also explains why the underlying sequence remains relevant. A view can refer to storage supplied elsewhere, and its iterator can depend on the view's predicate state. The convenient composed expression does not remove those lifetime relationships. The proposal includes viewable-range machinery to constrain conversions into views safely.

Filtering also changes which traversal capabilities remain available. The proposal's filter iterator can support backward traversal when the underlying range is bidirectional, but it does not preserve random access merely because the underlying storage has it. Finding the next selected element can require examining intervening values. An offset in the original sequence is not automatically the same offset in the filtered sequence. An algorithm must therefore use the capabilities of the adapted range, rather than assuming that every property of the original container survives composition.

Constraints include meaning that syntax cannot prove

The design combines requirements about valid expressions with semantic properties such as ordering and multipass traversal. A compiler can reject a missing operation, but satisfying a concept's syntactic test does not prove every intended semantic law for an arbitrary user-defined type.

P0896R3's sized-range discussion makes this visible through an opt-out customization. A type can have the required size expression while failing the intended constant-time or distance relationship. The customization lets authors prevent such a type from being classified as a sized range merely because its spelling matches the syntactic test.

Repeated traversal has a similar qualification. The proposal notes that a range whose iterator is a forward iterator can be passed to multiple algorithms and traversed repeatedly through its beginning and end operations. An input range without that stronger iterator property need not supply equivalent behavior after repeated calls. Describing an object as a range therefore does not promise that a second pass will see the same sequence. The forward-range requirement is the additional contract an algorithm needs when its work depends on making that second pass.

Sorting likewise depends on a strict weak ordering. The projection and comparison must jointly support the relationship expected by the algorithm. A callable that returns a Boolean is not automatically a meaningful ordering relation for every possible input. Constraints give that requirement a formal location without making all such semantic errors mechanically decidable.

I find that an important limit on claims about better diagnostics. Named requirements can make a failed interface easier to identify, but they do not eliminate the author's responsibility to supply the required behavior. The library contract becomes more explicit and reusable, while the program still has to meet its semantic part.

Existing algorithms remain alongside the constrained interfaces

The merge proposal places the new components in std::ranges while retaining the corresponding established interfaces in std. It also merges facilities from experimental headers into their appropriate standard headers and introduces the ranges header for the new range utilities and adaptors.

This organization provides a migration path that does not require replacing every old call immediately. A codebase can adopt a constrained algorithm where its projection or range overload is useful while keeping existing code that uses iterator pairs. The design avoids requiring one overnight change in the library vocabulary used by all dependencies.

The authors also discuss implementation experience. They acknowledge limited experience from major standard-library vendors and distinguish the range-v3 implementation from CMCSTL2, which uses language concepts directly. Their argument for adoption draws on both, including tests transferred between implementations and concept-specific defects found during that work.

Sutter's report confirms adoption while warning that some linked post-meeting papers will only become public later in the month. The pre-meeting proposal therefore supplies the mechanisms discussed here, while the report establishes the meeting outcome. Ranges have entered the working draft with a model for expressing algorithm requirements, and compiler and library support remain implementation work separate from that standards decision.