Ch8.5: Projections

Overview

A projection is a function applied to each element before an algorithm uses it. Instead of operating on the element directly, the algorithm operates on the projected value. This allows algorithms to work with derived fields or computed values without modifying the underlying data structure.

Projections are conceptually simple: the algorithm sees proj(x) instead of x. But understanding how projections interact with iterators, references, and algorithm semantics is essential for writing correct and efficient code.

1. What is a projection?

A projection is a unary function applied to each element before the algorithm processes it. For example, if we have a struct with two numeric fields, we can sort by one of them using a projection.


struct Record {
    ::std::size_t index;
    ::std::ptrdiff_t offset;
};

::fast_io::vector<Record> v = {
    {3, -10},
    {1,  25},
    {2,   5}
};

// Sort by the offset field
::std::ranges::sort(v, {}, &Record::offset);
      

The algorithm never compares Record objects directly. It compares the projected values returned by &Record::offset.

2. Why projections exist

Without projections, customizing algorithms often required:

Projections eliminate this boilerplate. They let you specify what part of the element the algorithm should use, without changing how the algorithm works.

3. Projections vs comparators

A comparator defines how two projected values are compared. A projection defines what part of the element is compared.


// Comparator only:
::std::ranges::sort(v, [](auto const& a, auto const& b) {
    return a.offset < b.offset;
});

// Comparator + projection:
::std::ranges::sort(v, {}, &Record::offset);
      

The projection version is shorter, avoids captures, and avoids duplicating field names. It also reduces binary size compared to lambdas.

4. Projections must be pure and cheap

Projections are called many times. They must be:

A projection should behave like a field access. Anything more expensive risks destroying algorithmic performance.


// Bad: expensive projection
::std::ranges::sort(v, {}, [](auto const& r) {
    return r.offset * r.offset * r.offset; // expensive
});
      

This turns O(n log n) sorting into something much worse.

5. Projections and references

A projection may return a reference, but only if the reference remains valid for the duration of the algorithm. Returning references to temporaries is undefined behavior.


// Bad: returns reference to a temporary
::std::ranges::sort(v, ::std::ranges::greater{}, [](auto const& r) -> ::std::ptrdiff_t const& {
    return r.offset + 1; // temporary
});
      

Safe projections return:

6. Projections and stability

Projections do not change whether an algorithm is stable. They only change what the algorithm sees. If two elements have equal projected values, a stable algorithm preserves their original order.

Key takeaways