Skip to content
C++
Language
since C++26
Intermediate

Pack Indexing (C++26)

C++26 pack indexing (P2662): access individual elements of parameter packs with pack...[N] — no more recursive TMP or std::tuple_element to grab the Nth type or value from a variadic pack.

Pack Indexing (P2662)since C++26

Pack indexing lets you write pack...[N] to access the Nth element of a template parameter pack or function parameter pack directly — replacing recursive std::tuple_element-style metaprogramming with clean subscript syntax.

The Problem It Solves

Before C++26, accessing the Nth element of a pack required either recursive TMP or std::tuple as an intermediary:

cpp
// Pre-C++26: cumbersome tuple_element trick
template<size_t N, typename... Ts>
using nth_type = std::tuple_element_t<N, std::tuple<Ts...>>;

// Pre-C++26: recursive instantiation
template<size_t N, typename First, typename... Rest>
struct nth {
    using type = typename nth<N-1, Rest...>::type;
};
template<typename First, typename... Rest>
struct nth<0, First, Rest...> {
    using type = First;
};

Both approaches create unnecessary template instantiations and compile-time overhead.

Syntax

cpp
// Type pack indexing — access Nth type
template<typename... Ts>
using third_type = Ts...[2];   // zero-indexed

// Value pack indexing — access Nth value
template<auto... Vs>
constexpr auto third_value = Vs...[2];

// In function parameters
template<typename... Ts>
void get_first(Ts... args) {
    auto first = args...[0];   // first element
}

The index N must be a constant expression in [0, sizeof...(pack)).

Practical Examples

Type Selection

cpp
template<size_t N, typename... Ts>
using at_t = Ts...[N];   // clean, no tuple needed

static_assert(std::is_same_v<at_t<1, int, float, double>, float>);
static_assert(std::is_same_v<at_t<0, char, bool>, char>);

Value Pack Access

cpp
template<auto... Ns>
constexpr auto sum_first_two() {
    return Ns...[0] + Ns...[1];
}

static_assert(sum_first_two<10, 20, 30>() == 30);

Function Argument Selection

cpp
// Return the last argument
template<typename... Ts>
auto last(Ts... args) {
    return args...[sizeof...(Ts) - 1];
}

static_assert(last(1, 2, 3, 4) == 4);
static_assert(last("hello", "world") == std::string_view{"world"});

Building Type-Safe Heterogeneous Access

cpp
#include <variant>

template<typename... Ts>
class TypeList {
public:
    // Access type at position N
    template<size_t N>
    using type_at = Ts...[N];

    // Construct variant of all types
    using variant_type = std::variant<Ts...>;

    // Check if Nth type matches
    template<size_t N, typename T>
    static constexpr bool is_at = std::is_same_v<type_at<N>, T>;
};

using MyTypes = TypeList<int, float, std::string>;
static_assert(std::is_same_v<MyTypes::type_at<2>, std::string>);
static_assert(MyTypes::is_at<0, int>);

Comparison: Before and After

cpp
// Before C++26 — requires external trait or recursive template
template<typename... Ts>
struct MyTuple {
    std::tuple<Ts...> data;
    
    template<size_t N>
    auto get() { return std::get<N>(data); }
    
    // Getting the type required std::tuple_element:
    template<size_t N>
    using value_type = std::tuple_element_t<N, std::tuple<Ts...>>;
};

// After C++26 — direct pack subscript
template<typename... Ts>
struct MyTuple {
    std::tuple<Ts...> data;
    
    template<size_t N>
    auto get() { return std::get<N>(data); }
    
    // Now just index the pack directly:
    template<size_t N>
    using value_type = Ts...[N];  // No tuple_element boilerplate
};

With Fold Expressions

Pack indexing composes well with fold expressions and expansion statements:

cpp
// Check if any type in pack is same as first
template<typename... Ts>
constexpr bool any_match_first = (std::is_same_v<Ts...[0], Ts> || ...);

// Swap first and last types
template<typename... Ts>
using swap_ends = TypeList<Ts...[sizeof...(Ts)-1], Ts...[1]..., Ts...[0]>;
// Note: middle elements need expansion (simplified example)

Interaction with auto Parameter Packs (C++20)

Pack indexing works with abbreviated function template packs too:

cpp
// C++20 abbreviated templates + C++26 pack indexing
auto first_arg(auto... args) {
    return args...[0];
}

auto last_arg(auto... args) {
    return args...[sizeof...(args) - 1];
}

static_assert(first_arg(10, 20, 30) == 10);
static_assert(last_arg(10, 20, 30)  == 30);

Key Rules

  • Index must be a constant expression — no runtime indexing of packs (that's what std::tuple and arrays are for)
  • Zero-indexed: first element is pack...[0]
  • Out-of-bounds index is a hard compile error (not UB)
  • Works for both type packs (typename... Ts) and value packs (auto... Vs, int... Ns)
  • The ... is part of the syntax — pack...[N], not pack[N]
  • A pack of size 0 cannot be indexed (compile error); check sizeof...(pack) > N first if needed