View Adapters vs View Factories in C++ Ranges
The C++20 Ranges library introduced a modern, composable way to work with sequences of data using views—lightweight, non-owning objects that operate lazily. Within this system, views are primarily constructed using two distinct mechanisms: view factories and view adapters. While view factories generate brand-new ranges out of scalar values or state, view adapters take an existing range and transform or filter it into a new range. Understanding the architectural difference between the two is essential for writing efficient, idiomatic range pipelines in modern C++.
What Is a View Factory?
A view factory is a function object that creates a view from scratch, without requiring an existing range as input. It serves as an origin point or data producer in a ranges pipeline. View factories typically accept arguments like values, boundaries, or types to construct a sequence dynamically and lazily on demand.
Common examples of standard view factories include:
std::views::iota: Produces an infinite or bounded sequence of sequentially increasing values starting from an initial element (e.g.,std::views::iota(1, 10)).std::views::single: Creates a view containing exactly one element provided as an argument.std::views::empty: Produces an empty view of a specified type.std::views::istream: Creates a view that reads elements iteratively from an input stream.std::views::repeat(introduced in C++23): Repeatedly yields the same value indefinitely or a bounded number of times.
Because view factories create ranges out of non-range inputs, they
cannot be placed on the right-hand side of a pipe operator
(|) to consume an upstream range. Instead, they appear at
the very beginning of a pipeline:
auto numbers = std::views::iota(1, 100); // View factory creating numbers from 1 to 99What Is a View Adapter?
A view adapter is a utility that takes an existing range as input and produces a new view that wraps, transforms, or filters the underlying elements. Unlike factories, adapters always require a source range to operate.
View adapters are designed to be composable. They can be invoked
directly as normal functions or chained together using the pipe operator
(|).
Common examples of standard view adapters include:
std::views::transform: Applies a transformation function to each element in the input range.std::views::filter: Yields only elements from the input range that satisfy a given predicate.std::views::take: Takes the first N elements from a range and ignores the rest.std::views::drop: Skips the first N elements of a range and yields the remaining elements.std::views::reverse: Iterates over an bidirectional range in reverse order.
Because view adapters take a range as their first argument, they can accept inputs from standard containers or from ranges produced by other views:
std::vector<int> data = {1, 2, 3, 4, 5};
auto evens = data | std::views::filter([](int n) { return n % 2 == 0; }); // View adapterKey Differences Summarized
| Characteristic | View Factory | View Adapter |
|---|---|---|
| Input Requirement | Values, states, or parameters (non-ranges) | An existing range (plus optional arguments) |
| Pipeline Position | Source (the start of a pipeline) | Intermediate step or final transformation |
| Piping Mechanism | Cannot consume input via the
| operator |
Can consume input via the |
operator |
| Primary Purpose | Generate a sequence of elements | Transform, filter, or reorder an existing sequence |
In practice, range pipelines frequently combine both concepts: a view factory initializes the data stream, and one or more view adapters reshape that stream into the desired output.