язык — обобщённый код над кортежамиlanguage — generic code over tuples
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.
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.
// нужно либо знать точную арность заранее, либо городить 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
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
Отсутствие пакетов в structured bindings было конкретным, ощутимым пробелом:The absence of packs in structured bindings was a specific, tangible gap:
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.
Единственным обходным путём было падать обратно на 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.
Поддержка пакетов делает 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.
В 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
Классический паттерн "обработать первый, рекурсировать на остальные", теперь напрямую выразимый без 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
}
}
Код, которому обобщённо нужна только первая размерность точки любой арности — 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
}
Функция парсинга возвращает кортеж "статус плюс переменное число значений" — статус именуется, а весь 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
}
Тот же результат в 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 + ...);
Это работает только для типов, совместимых со 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 внутри тела — это настоящий параметрический пакет, а не контейнер: в него нельзя проиндексироваться в рантайме, его можно только развернуть через 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.
Комбинация с уже существующей семантикой связывания 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.