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++26Pack 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:
// 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
// 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
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
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
// 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
#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
// 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:
// 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:
// 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::tupleand 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], notpack[N] - A pack of size 0 cannot be indexed (compile error); check
sizeof...(pack) > Nfirst if needed