язык — обобщённый код над кортежамиlanguage — generic code over tuples

Pack в structured bindingsPack in Structured Bindings

P1061 расширяет structured bindings (../cpp17/structured-bindings.html), позволяя захватывать переменное число "остальных" элементов как пакет: auto [first, ...rest] = tuple; — first именует первый элемент, а rest становится параметрическим пакетом из всех оставшихся.P1061 extends structured bindings (../cpp17/structured-bindings.html) to allow capturing a variable-length "rest" of elements as a pack: auto [first, ...rest] = tuple; — first names the first element by name, and rest becomes a parameter pack containing all remaining elements.

закрывает пробел между structured bindings и variadic-шаблонамиcloses the gap between structured bindings and variadic templates точные правила размещения пакета — деталь формулировки, ещё может уточнятьсяthe exact pack-placement rules are a wording detail, still subject to refinement частый вопрос: почему это нельзя было сделать через std::apply раньшеcommon question: why this couldn't already be done via std::apply

Что этоWhat it is

P1061 добавляет к синтаксису structured bindings возможность одного элемента списка быть пакетом, помеченным многоточием: auto [first, ...rest] = some_tuple;. Здесь first связывается с первым элементом по имени как обычно, а rest становится параметрическим пакетом, содержащим все оставшиеся элементы — пригодным для использования в fold-выражении (../cpp17/fold-expressions.html) или для дальнейшего разворачивания в другом variadic-контексте.P1061 adds to the structured-bindings syntax the ability for one element of the binding list to be a pack, marked with an ellipsis: auto [first, ...rest] = some_tuple;. Here first binds to the first element by name as usual, and rest becomes a parameter pack containing all remaining elements — usable in a fold expression (../cpp17/fold-expressions.html) or for further expansion in another variadic context.

до C++26 — только полное перечисление имёнbefore C++26 — only a full enumeration of names
// нужно либо знать точную арность заранее, либо городить std::apply + index_sequence// you either need the exact arity in advance, or resort to std::apply + index_sequence
auto t = std::make_tuple(1, 2, 3, 4);
auto [a, b, c, d] = t;  // работает только если арность известна и фиксирована здесь же// only works if the arity is known and fixed right here
auto sum = b + c + d;  // "все кроме первого" нужно перечислить вручную по именам// "everything but the first" must be listed by name manually
C++26 — pack захватывает "остальное" напрямуюC++26 — a pack captures "the rest" directly
auto t = std::make_tuple(1, 2, 3, 4);
auto [first, ...rest] = t;  // first == 1, rest — пакет {2, 3, 4}// first == 1, rest is the pack {2, 3, 4}
auto sum = (rest + ...);       // fold-выражение прямо над пакетом из structured binding// a fold expression directly over the structured-binding pack

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

Отсутствие пакетов в structured bindings было конкретным, ощутимым пробелом:The absence of packs in structured bindings was a specific, tangible gap:

01

C++17 требовал именовать КАЖДЫЙ элементC++17 required naming EVERY element

Structured bindings в C++17 требовали дать имя каждому элементу разбираемого объекта — для кортежа/массива неизвестного или большого фиксированного размера, или для обобщённого кода, работающего над кортежами разной арности, это делало structured bindings неприменимыми в шаблонных контекстах: "хочу первый элемент и не забочусь, сколько ещё" не имело прямого синтаксиса.Structured bindings in C++17 required naming every element of the object being destructured — for a tuple/array of unknown or large fixed size, or for generic code operating over tuples of varying arity, this made structured bindings unusable in template contexts: "I want the first element and don't care how many more there are" had no direct syntax.

02

Обходной путь — std::get<I>... вручную, теряя лаконичностьThe workaround — manual std::get<I>..., losing the conciseness

Единственным обходным путём было падать обратно на std::get<0>/std::get<I>... вручную или на std::apply с std::index_sequence — рабочее решение, но полностью теряющее ту самую лаконичность, ради которой structured bindings и вводились.The only workaround was falling back to std::get<0>/std::get<I>... by hand, or to std::apply with std::index_sequence — a working solution, but one that entirely loses the conciseness structured bindings were introduced for in the first place.

03

Pack закрывает пробел, делая structured bindings arity-независимымиA pack closes the gap, making structured bindings arity-agnostic

Поддержка пакетов делает structured bindings пригодными для по-настоящему обобщённого, независимого от арности кода — по аналогии с тем, как обычные параметрические пакеты функций позволяют писать вариативный код, не зная число аргументов заранее.Pack support makes structured bindings usable in genuinely generic, arity-agnostic code — mirroring how ordinary function parameter packs let you write variadic code without knowing the argument count ahead of time.

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

В auto [first, ...rest] = std::make_tuple(1, 2, 3, 4); rest становится пакетом из {2, 3, 4}, который дальше разворачивается через fold-выражение: auto sum = (rest + ...);. Разрешён ровно один пакет на структурированное связывание — как и с обычными параметрическими пакетами функций, где после пакета не может идти ещё один пакет. Точное правило о том, где именно в списке может стоять пакет (только в конце, или где угодно при условии единственности) — деталь формулировки предложения, которая на момент написания ещё может уточняться; здесь стоит хеджировать честно, а не утверждать конкретную финальную позицию как гарантированную.In auto [first, ...rest] = std::make_tuple(1, 2, 3, 4);, rest becomes a pack of {2, 3, 4}, which is then expanded via a fold expression: auto sum = (rest + ...);. Exactly one pack is permitted per structured binding — much like ordinary function parameter packs, where no further pack may follow a pack. The exact rule for where in the list a pack may appear (only at the end, or anywhere provided it's the sole one) is a proposal wording detail that, as of this writing, may still be refined — this should be hedged honestly rather than stating one final position as guaranteed.

auto t = std::make_tuple(1, 2, 3, 4);
auto [first, ...rest] = t;   // first == 1, rest — пакет из трёх элементов: {2, 3, 4}// first == 1, rest is a pack of three elements: {2, 3, 4}

auto sum = (rest + ...);    // fold-выражение над пакетом: 2 + 3 + 4 == 9// a fold expression over the pack: 2 + 3 + 4 == 9

// разрешён только один пакет на связывание — второй "...", как и с// only one pack is permitted per binding — a second "...", like with
// обычными параметрическими пакетами функций, был бы ошибкой компиляции// ordinary function parameter packs, would be a compile error

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

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

Рекурсия "первый плюс хвост" над кортежем на этапе компиляцииCompile-time "head plus tail" recursion over a tuple

Классический паттерн "обработать первый, рекурсировать на остальные", теперь напрямую выразимый без std::get на каждом уровне рекурсии.The classic "process first, recurse on rest" pattern, now directly expressible with no std::get at every recursion level.

template<typename... Ts>
void print_all(const std::tuple<Ts...>& t) {
    if constexpr (sizeof...(Ts) > 0) {
        auto& [first, ...rest] = t;
        std::cout << first << " ";
        if constexpr (sizeof...(rest) > 0)
            print_all(std::tie(rest...));  // рекурсия на "хвост" без ручного индексирования// recursing on the "tail" with no manual indexing
    }
}
02

Вариативно-арная координата, разбираемая на голову и хвостA variadic-arity coordinate destructured into head and tail

Код, которому обобщённо нужна только первая размерность точки любой арности — 2D, 3D, N-мерной.Code that generically only needs a point's first dimension, regardless of whether the point is 2D, 3D, or N-dimensional.

template<typename Point>
auto first_axis(const Point& point) {
    auto& [x, ...trailing] = point;
    return x;  // не важно, сколько ещё измерений в trailing// doesn't matter how many more dimensions are in trailing
}
03

Суммирование payload'а с фиксированным статус-элементом впередиSumming a payload with a fixed status element up front

Функция парсинга возвращает кортеж "статус плюс переменное число значений" — статус именуется, а весь payload сворачивается fold-выражением.A parsing function returns a "status plus variable-length values" tuple — the status is named, and the whole payload is folded.

auto [status, ...values] = parse_record(line);
if (status == ParseStatus::Ok) {
    auto total = (values + ...);  // сумма всего переменного payload'а// the sum of the entire variable-length payload
}
04

Сравнение с эквивалентом C++17 через std::applyContrast with the C++17-only std::apply equivalent

Тот же результат в C++17 требовал std::apply с лямбдой на все элементы плюс отдельную свёртку — заметно больше шаблонного кода ради того же результата.The same result in C++17 required std::apply with a lambda over all elements plus a separate fold — noticeably more boilerplate for the same result.

// C++17 — без пакетов в structured bindings// C++17 — without packs in structured bindings
auto sum_tail_cpp17 = std::apply([](auto first, auto... rest) {
    return (rest + ...);
}, t);

// C++26 — прямо через structured binding, без apply и без лямбды-обёртки// C++26 — directly via a structured binding, no apply, no wrapper lambda
auto [first, ...rest] = t;
auto sum_tail_cpp26 = (rest + ...);

ГраблиGotchas

Требует известную на этапе компиляции арность — не работает для runtime-размераRequires a compile-time-known arity — doesn't work for a runtime size

Это работает только для типов, совместимых со structured bindings и имеющих известную, фиксированную на этапе компиляции арность (кортежи, массивы, tuple-like типы через существующий протокол structured bindings) — это НЕ позволяет разобрать std::vector произвольного времени выполнения размера на голову и пакет, поскольку structured bindings фундаментально требуют арности, известной на этапе компиляции.This only works for structured-binding-compatible types with a known, compile-time-fixed arity (tuples, arrays, tuple-like types via the existing structured-bindings protocol) — it does NOT let you destructure a runtime-sized std::vector into a head plus a pack, since structured bindings fundamentally require a compile-time-known arity.

rest — настоящий параметрический пакет, не контейнер с индексациейrest is a genuine parameter pack, not an indexable container

Пакет rest внутри тела — это настоящий параметрический пакет, а не контейнер: в него нельзя проиндексироваться в рантайме, его можно только развернуть через fold-выражение или другую пакето-потребляющую конструкцию на этапе компиляции — частый источник путаницы у тех, кто ожидает контейнероподобной индексации.The rest pack inside the body is a genuine parameter pack, not a container — you can't index into it at runtime, only expand it via a fold expression or another pack-consuming construct at compile time, a common point of confusion for people expecting container-like indexing.

Ссылочная семантика применяется единообразно ко всему пакетуReference semantics apply uniformly to the whole pack

Комбинация с уже существующей семантикой связывания structured bindings (auto&, const auto&) применяется единообразно ко всему списку, включая пакет: каждый элемент пакета связывается тем же способом, что и first — нельзя смешать значение для first и ссылку для rest (или наоборот) в одном объявлении.Combining this with structured bindings' existing reference-binding semantics (auto&, const auto&) applies uniformly to the whole list, including the pack: every element of the pack binds the same way as first — you cannot mix a by-value first with a by-reference rest (or vice versa) in one declaration.