Understanding std::views::elements in C++

The std::views::elements adaptor in C++20 provides a generic way to project a specific component out of a sequence of tuple-like objects. This article explains what std::views::elements extracts from collections of tuples or pairs, demonstrates its syntax with compile-time indexing, and examines its relationship with common utility views like std::views::keys and std::views::values.

What std::views::elements Extracts

The std::views::elements<N> range adaptor takes a view of tuple-like elements (such as std::tuple, std::pair, or std::array) and produces a new view containing only the \(N\)-th element of each entry. The index N is a zero-based compile-time constant provided as a template argument.

For any element \(E\) in the source range, std::views::elements<N> accesses the component using the equivalent of std::get<N>(E).

Working with std::pair

When applied to a collection of std::pair objects, std::views::elements extracts either the first or second member based on the provided index:

  • std::views::elements<0> extracts the first member of every pair.
  • std::views::elements<1> extracts the second member of every pair.
#include <iostream>
#include <vector>
#include <utility>
#include <ranges>

int main() {
    std::vector<std::pair<int, std::string>> data = {
        {1, "One"}, {2, "Two"}, {3, "Three"}
    };

    // Extract the second element (std::string)
    for (const auto& name : data | std::views::elements<1>) {
        std::cout << name << ' '; // Outputs: One Two Three
    }
}

Working with std::tuple and Larger Structures

Unlike standard pair accessors, std::views::elements generalizes to tuples and arrays of any size, allowing extraction of arbitrary fields:

#include <iostream>
#include <vector>
#include <tuple>
#include <ranges>

int main() {
    std::vector<std::tuple<int, std::string, double>> employees = {
        {101, "Alice", 75000.0},
        {102, "Bob", 82000.0},
        {103, "Charlie", 68000.0}
    };

    // Extract the third element (double salary)
    for (double salary : employees | std::views::elements<2>) {
        std::cout << salary << ' '; // Outputs: 75000 82000 68000
    }
}

Attempting to access an index out of bounds (for instance, std::views::elements<3> on a 3-element tuple) results in a compile-time error.

Relationship to keys and values

C++ provides dedicated aliases for the two most common use cases involving two-element structures:

  • std::views::keys is an alias for std::views::elements<0>.
  • std::views::values is an alias for std::views::elements<1>.

These aliases are primarily used when iterating over associative containers like std::map or std::unordered_map, where the elements are stored as std::pair<const Key, Value>.

Key Characteristics

  • Lazy Evaluation: std::views::elements does not copy or store elements; it projects values on the fly during iteration.
  • Value Category Preservation: If the underlying range provides lvalue references, the view yields lvalue references, allowing in-place modification of non-const elements.
  • Tuple-like Concept: The adaptor operates on any type that models the internal tuple-like protocol, meaning it supports std::tuple_size, std::tuple_element, and std::get.