шаблоны и пакеты параметровtemplates and parameter packs
Прямой синтаксис индексации пакета параметров шаблона и функционального пакета параметров — 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.
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.
// "тип по индексу 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;
template<typename... Ts>
using First = Ts...[0]; // один шаблон, никакой рекурсии// one template, no recursion
template<auto... Vals>
constexpr auto second = Vals...[1]; // то же для пакета значений// same for a value pack
До 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.
Альтернативой было сначала материализовать пакет в 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".
Прямая индексация убирает и рекурсивный бойлерплейт, и материализацию через 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.
Синтаксис 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
}
Извлечение первого и последнего типа из пакета параметров вариативного шаблона для 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];
};
Специализированная диспетчеризация всегда трактует 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();
}
Сравнение объёма кода для одной и той же задачи "тип по индексу 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];
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
}
Это индексация этапа компиляции, а не 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.
Индекс, равный или превышающий размер пакета, — это ошибка компиляции, а не неопределённое поведение — в отличие, скажем, от выхода за границы сырого массива или 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.
Синтаксис применяется к пакету параметров шаблона/функции как языковой сущности, до/без материализации. Если пакет уже превращён в 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.