Simplify C++ Map Traversal with std::views::keys
Iterating over associative containers like std::map or
std::unordered_map has historically required accessing
entire key-value pairs, even when only the keys are needed. With the
introduction of the Ranges library in C++20,
std::views::keys provides a modern, readable, and efficient
way to extract and traverse keys directly. This article explains how
std::views::keys simplifies map traversal, compares it to
older techniques, and highlights its composability within the ranges
ecosystem.
The Problem with Traditional Traversal
In associative containers, elements are stored as
std::pair<const Key, Value>. Prior to C++20,
accessing only the keys required either referencing
pair.first manually or using C++17 structured bindings
where the value component was left unused:
std::map<std::string, int> scores = {{"Alice", 95}, {"Bob", 80}, {"Charlie", 90}};
// Pre-C++17: Verbose pair access
for (const auto& pair : scores) {
process(pair.first);
}
// C++17: Structured bindings with unused variables
for (const auto& [key, value] : scores) {
process(key); // 'value' is unused and can trigger compiler warnings
}While functional, both approaches pollute the loop scope with unnecessary information and fail to express direct intent.
How std::views::keys
Solves It
Defined in the <ranges> header,
std::views::keys is a range adaptor object. It functions as
an alias for std::views::elements<0>, specifically
targeting the first element of any tuple-like structure, such as
std::pair.
When applied to a map, it produces a view containing only the keys:
#include <iostream>
#include <map>
#include <ranges>
#include <string>
int main() {
std::map<std::string, int> scores = {{"Alice", 95}, {"Bob", 80}, {"Charlie", 90}};
for (const auto& key : scores | std::views::keys) {
std::cout << key << '\n';
}
}Key Advantages
- Clear Expressiveness: The loop variable directly
represents the key. There are no unused variables, suppressing potential
compiler warnings without needing
[[maybe_unused]]. - Zero Allocation and Lazy Evaluation:
std::views::keysdoes not create a new container or allocate memory. It operates lazily, projecting each key on demand as iteration occurs. - Pipeline Composability: Because it conforms to the
range adaptor interface, it can be chained using the pipe operator
(
|) with other views and standard algorithms:
// Filter and print only keys starting with 'A'
for (const auto& key : scores | std::views::keys
| std::views::filter([](const std::string& k) { return k.starts_with('A'); })) {
std::cout << key << '\n';
}By decoupling the key from the associated value at the iteration
interface, std::views::keys transforms map traversal from a
verbose workaround into clean, idiomatic modern C++.