With C++26 introducing structured binding packs (P1061R10) and pack indexing (P2662R3), template metaprogramming has become much cleaner. However, using these bleeding-edge features can sometimes trigger unexpected compiler issues when working with nested generic lambdas.

If you encounter an error like error: trying to capture 'args#0' in instantiation of generic lambda when using implicit lambda captures ([&]), here is an explanation of why this happens and how to resolve it.

The Problem: Implicit Capture Fails on Structured Binding Packs

Consider the following modern C++26 pattern where a structured binding pack is unpacked within nested lambdas using pack indexing:

#include <tuple>
#include <utility>
#include <cstddef>

template<typename... T>
void test()
{
    auto tpl = std::tuple<T...>{};
    auto& [...args] = tpl;

    [&]<std::size_t... Is>(std::index_sequence<Is...>)
    {
        ([&] // unfolding index pack
        {
            constexpr auto IDX = Is;
            auto& dst = args...[IDX]; // <-- GCC Error here
            // ... work with dst
        }(), ...);
    }(std::index_sequence_for<T...>{});
}

int main()
{
    test<int, int>();
}

When compiling with GCC using -std=c++26, GCC throws:

error: trying to capture ‘args#0’ in instantiation of generic lambda
   auto& dst = args...[IDX];
               ^~~~

Why Does This Happen?

This is a compiler bug in GCC's experimental C++26 implementation. According to the C++ standard, a default capture [&] should automatically capture any odr-used variable from the enclosing scope, including structured binding packs and their elements.

However, GCC's frontend fails to track the implicit capture dependency through multiple nested closure layers when pack indexing (args...[IDX]) is involved. Because the inner lambda accesses an indexed decomposed element (args#0), the compiler fails to register that the outer generic lambda also needed to implicitly capture the pack.

Solutions and Workarounds

1. Use Explicit Capture in the Lambda Header (Recommended)

The cleanest fix is to explicitly capture the parameter pack in the outer lambda using [&args...]. This forces GCC to establish the closure capture upfront before the inner lambda evaluates:

// Explicitly capture args... in the outer lambda
[&args...]<std::size_t... Is>(std::index_sequence<Is...>)
{
    ([&args...]
    {
        constexpr auto IDX = Is;
        auto& dst = args...[IDX];
        // ...
    }(), ...);
}(std::index_sequence_for<T...>{});

2. Capture the Underlying Container Directly

If you are decomposing a tuple or standard aggregate, you can capture the tuple reference instead of the pack, deferring decomposition or using std::get directly inside the inner loop:

auto tpl = std::tuple<T...>{};

[&tpl]<std::size_t... Is>(std::index_sequence<Is...>)
{
    ([&tpl]
    {
        auto& dst = std::get<Is>(tpl);
        // ...
    }(), ...);
}(std::index_sequence_for<T...>{});

3. Pass Elements as Arguments Rather Than Capturing

Another clean approach is to forward the pack elements directly via lambda parameter lists, avoiding capture nesting entirely:

[&](auto&... elems)
{
    [&]<std::size_t... Is>(std::index_sequence<Is...>)
    {
        ([&](auto& dst)
        {
            // Use dst directly without pack indexing
        }(elems...[Is]), ...);
    }(std::index_sequence_for<T...>{});
}(args...);

Summary

  • Implicit capture ([&]) with structured binding packs across nested generic lambdas currently triggers an internal capture resolution bug in GCC.
  • The standard compliant and simplest workaround is to explicitly write [&args...] in your lambda capture lists.
  • Compilers like Clang already handle nested implicit captures of structured binding packs correctly in newer trunk versions.