std::views::values in C++ Associative Ranges
Introduced in C++20, std::views::values is a range
adaptor that extracts the second element from pair-like or tuple-like
elements across a range, making it particularly useful for associative
containers like std::map and
std::unordered_map. This article covers the primary role of
std::views::values, how it eliminates boilerplate code, its
lazy evaluation semantics, and how it enables clean range-based
pipelines when working with key-value data.
The Core Role of std::views::values
Associative containers store their elements as pairs—typically
std::pair<const Key, Value>. Prior to modern range
adaptors, iterating exclusively over the values stored in a map required
explicitly unpacking the pair using structured bindings or accessing the
.second member manually:
std::map<std::string, int> scores = {{"Alice", 95}, {"Bob", 88}, {"Charlie", 92}};
// Traditional approach
for (const auto& [name, score] : scores) {
// 'name' is declared even if unused
std::cout << score << '\n';
}The role of std::views::values is to project a range of
key-value pairs into a view containing only the values. It applies
std::get<1> conceptually to each element in the input
range, discarding the key from downstream operations.
Clean Integration with Range Pipelines
Because std::views::values returns a range adaptor
closure, it can be composed using the pipe (|) operator.
This allows you to chain it with other adaptors, such as
std::views::filter or std::views::transform,
without needing to account for the key component in subsequent
operations.
#include <iostream>
#include <map>
#include <ranges>
#include <numeric>
int main() {
std::map<std::string, int> inventory = {
{"Apples", 50},
{"Oranges", 0},
{"Bananas", 25},
{"Grapes", 0}
};
// Filter and process only the quantities (values)
auto in_stock = inventory
| std::views::values
| std::views::filter([](int count) { return count > 0; });
for (int count : in_stock) {
std::cout << count << '\n'; // Outputs: 50, 25
}
}Reference Preservation and Lazy Evaluation
std::views::values operates lazily. It does not copy or
allocate an intermediate container for the values. Instead, it generates
values on demand as the range is traversed.
Additionally, std::views::values preserves the reference
qualifiers of the underlying values:
- When operating over a non-const map, the view yields non-const
references (
Value&), allowing in-place modification of map values. - When operating over a const map, it yields
const Value&.
Unlike the keys in associative containers—which are marked
const to protect container invariants—the mapped values are
fully mutable through the view:
std::map<std::string, int> data = {{"A", 1}, {"B", 2}};
// Mutating values directly through the view
for (int& val : data | std::views::values) {
val *= 10;
}
// 'data' now contains {"A", 10} and {"B", 20}Summary of Benefits
- Intent Clarity: Signals immediately that the algorithm or loop operates solely on the payload data, ignoring keys.
- Composability: Integrates directly with standard
range pipelines and constrained algorithms like
std::ranges::copyorstd::ranges::fold_left. - Zero Overhead: Acts as a lightweight wrapper that iterates over the source container without allocations or unnecessary data copying.