шаблоны и пакеты параметровtemplates and parameter packs

Pack Indexing

Прямой синтаксис индексации пакета параметров шаблона и функционального пакета параметров — Pack...[N] — возвращает N-й элемент пакета на этапе компиляции, без рекурсивного шаблона-помощника и без предварительной материализации в std::tuple.Direct indexing syntax for template parameter packs and function parameter packs — Pack...[N] — yields the Nth pack element at compile time, with no recursive helper template and no need to first materialize the pack into a std::tuple.

убирает классический бойлерплейт метапрограммированияremoves classic metaprogramming boilerplate частый вопрос: чем это лучше std::tuple_element/std::getcommon question: how this beats std::tuple_element/std::get

Что этоWhat it is

P2662 добавляет прямую индексацию пакетов параметров шаблона и функциональных пакетов параметров: Pack...[N] сразу даёт N-й элемент пакета на этапе компиляции — без рекурсивного шаблона-помощника и без std::tuple_element/std::get над предварительно материализованным кортежем.P2662 adds direct indexing for template parameter packs and function parameter packs: Pack...[N] directly yields the Nth pack element at compile time — with no recursive helper template and no std::tuple_element/std::get over a pre-materialized tuple.

до C++26 — рекурсивный шаблон-помощникbefore C++26 — a recursive helper template
// "тип по индексу N" — классическая рекурсия, снимающая по одному элементу// "type at index N" — classic recursion peeling one element at a time
template<std::size_t N, typename T, typename... Rest>
struct NthType : NthType<N - 1, Rest...> {};

template<typename T, typename... Rest>
struct NthType<0, T, Rest...> { using type = T; };

template<typename... Ts>
using First = typename NthType<0, Ts...>::type;
C++26 — прямая индексация пакетаC++26 — direct pack indexing
template<typename... Ts>
using First = Ts...[0];   // один шаблон, никакой рекурсии// one template, no recursion

template<auto... Vals>
constexpr auto second = Vals...[1];   // то же для пакета значений// same for a value pack

Зачем ввелиWhy it was introduced

01

Рекурсивное метапрограммирование как единственный путьRecursive metaprogramming as the only path

До C++26 получить "N-й тип/значение в пакете" можно было только рекурсивной шаблонной метапрограммой — классический, многословный паттерн: снимать по одному элементу за раз, пока не дойдёшь до индекса 0.Before C++26, getting "the Nth type/value in a pack" could only be done via recursive template metaprogramming — a classic, verbose pattern: peel off one element at a time until index 0 is reached.

02

Или материализация в std::tuple как обходной путьOr materializing into std::tuple as a workaround

Альтернативой было сначала материализовать пакет в std::tuple, а затем использовать std::get<N>/std::tuple_element_t<N, ...> — лишний уровень косвенности, который стоит и времени компиляции, и ясности кода, когда вся задача — "дай мне элемент 3".The alternative was to first materialize the pack into a std::tuple, then use std::get<N>/std::tuple_element_t<N, ...> — an extra layer of indirection costing both compile time and code clarity when all you wanted was "give me element 3".

03

Очень частая операция превращается в однострочникA very common operation becomes a one-liner

Прямая индексация убирает и рекурсивный бойлерплейт, и материализацию через tuple, превращая крайне распространённый кусок шаблонного метапрограммирования в одну строку кода.Direct indexing removes both the recursive boilerplate and the tuple-materialization indirection, turning a very common piece of template metaprogramming into a one-liner.

Как это работает под капотомHow it works under the hood

Синтаксис Pack...[Index] применяется как к пакетам параметров типов, так и к пакетам значений (non-type template parameter packs), а также к функциональным пакетам параметров внутри тела функции — индекс должен быть константой этапа компиляции.The Pack...[Index] syntax applies to type template parameter packs, to value (non-type template parameter) packs, and to function parameter packs inside a function body — the index must be a compile-time constant.

template<typename... Ts>
using First = Ts...[0];

template<auto... Vals>
constexpr auto second = Vals...[1];

// индексация функционального пакета параметров внутри тела функции// indexing a function parameter pack inside a function body
template<typename... Args>
auto& pick_first(Args&... args) {
    return args...[0];   // первый параметр по позиции, без std::get// the first parameter by position, no std::get
}

Примеры из реального кодаReal-world examples

типичные сценарии использованияtypical usage scenarios
01

Head/tail-last типаж без рекурсииA head/tail-last trait with no recursion

Извлечение первого и последнего типа из пакета параметров вариативного шаблона для type trait — без единого уровня рекурсивной специализации.Extracting the first and last type from a variadic template's parameter pack for a type trait — with no recursive specialization at all.

template<typename... Ts>
struct PackEnds {
    using head = Ts...[0];
    using tail_last = Ts...[sizeof...(Ts) - 1];
};
02

Выбор конкретного позиционного аргументаPicking a specific positional argument

Специализированная диспетчеризация всегда трактует 2-й аргумент вариативной функции как callback, независимо от общего числа переданных аргументов.Specialized dispatch always treats the 2nd argument of a variadic function as a callback, regardless of how many total arguments were passed.

template<typename... Args>
void dispatch_with_callback(Args&&... args) {
    auto&& callback = args...[1];   // второй аргумент всегда callback// the second argument is always the callback
    callback();
}
03

Старая рекурсия против новой индексации бок о бокOld recursion vs. new indexing side by side

Сравнение объёма кода для одной и той же задачи "тип по индексу N" делает сокращение бойлерплейта наглядным.Comparing code volume for the same "type at index N" task makes the boilerplate reduction visually concrete.

// до: специализация + базовый случай, минимум 2 шаблона// before: specialization + base case, at least 2 templates
template<std::size_t N, typename... Ts>
struct TypeAt { using type = typename NthType<N, Ts...>::type; };

// после: одна строка, компилятор делает всю работу// after: one line, the compiler does all the work
template<std::size_t N, typename... Ts>
using TypeAtN = Ts...[N];
04

Выбор конкретных элементов вместо свёртки по всемSelecting specific elements instead of folding over all

Fold-выражение сворачивает весь пакет единообразно; индексация пакета позволяет выбрать и скомбинировать конкретные элементы по позиции, когда нужна не свёртка, а точечный доступ.A fold expression folds the whole pack uniformly; pack indexing lets you select and combine specific elements by position when you need targeted access instead of a fold.

template<typename... Ts>
auto combine_first_and_last(Ts... values) {
    return values...[0] + values...[sizeof...(Ts) - 1];   // только два конкретных элемента, не свёртка по всем// only two specific elements, not a fold over all
}

ГраблиGotchas

Индекс обязан быть compile-time константойThe index must be a compile-time constant

Это индексация этапа компиляции, а не runtime-доступ по типу массива — попытка проиндексировать пакет значением, вычисленным во время выполнения, является ошибкой компиляции. Частая точка путаницы для тех, кто привык к рантайм-индексации контейнеров.This is compile-time indexing, not a runtime array-like access — attempting to index a pack with a runtime-computed value is a compile error. A common point of confusion for people used to runtime container indexing.

Выход за границы — ошибка компиляции, а не UBOut-of-bounds indexing is a compile error, not UB

Индекс, равный или превышающий размер пакета, — это ошибка компиляции, а не неопределённое поведение — в отличие, скажем, от выхода за границы сырого массива или std::vector во время выполнения. Это реальное улучшение безопасности по сравнению с аналогичными паттернами времени выполнения, а не только предостережение.An index equal to or exceeding the pack size is a compile-time error, not undefined behavior — unlike, say, indexing past the end of a raw array or std::vector at runtime. This is a genuine safety improvement over analogous runtime patterns, worth calling out positively.

Работает на самом пакете, не на материализованном контейнереWorks on the pack itself, not on a materialized container

Синтаксис применяется к пакету параметров шаблона/функции как языковой сущности, до/без материализации. Если пакет уже превращён в std::tuple или std::array, снова нужны std::get/operator[] этого типа-контейнера — новый синтаксис их не заменяет для уже материализованных данных.The syntax applies to the template/function parameter pack as a language-level entity, before/without materialization. If the pack has already been turned into a std::tuple or std::array, you're back to std::get/operator[] on that container type — the new syntax doesn't replace them for already-materialized data.