c++c++17c++20template-meta-programming

std::integer_sequence in a lambda before C++20


I have started using this type of construction, which depends on C++20's explicit template parameters for lambdas:

template<typename... Ts>
struct Foo
{
  std::tuple<Ts...> bars;

  auto get_labels(const std::array<std::size_t,sizeof...(Ts)>& indices) const
  {
    // construct tuple from bar labels
    return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
        return std::make_tuple(std::get<Is>(bars).get_label(indices[Is])...);
      }(std::index_sequence_for<Ts...>{});
  }
};

Compiler Explorer example

How can I do this in C++17 or C++14 (without explicit template parameters for lambdas)?


Solution

  • So it's important to recognize what part you actually need. When you write this:

    // construct tuple from bar labels
    return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
        return std::make_tuple(std::get<Is>(bars).get_label(indices[Is])...);
    }(std::index_sequence_for<Ts...>{});
    

    What you actually need is Is.... Or, more generally, what you actually need is a pack of constant values. In the above example, you are accepting a parameter of index_sequence<Is...> - that's one parameter, whose type has the pack of constant values in it.

    But a different way to do it would be to accept N different parameters, where the first parameter is of type integral_constant<size_t, 0> and the second parameter is of type integral_constant<size_t, 1>, and so forth. If you could produce those arguments, then the lambda part becomes

    [&](auto... Is){
        return std::make_tuple(std::get<Is>(bars).get_label(indices[Is])...);
    }
    

    Note that the body is identical, I just changed what the parameter(s) looks like. And this is now a valid C++14 lambda.

    So the rest of the problem is producing the function template with<N>(f), which calls f(integral_constant<size_t, 0>{}, integral_constant<size_t, 1>{},..., integral_constant<size_t, N-1>{}) which allows you to call:

    return with<sizeof...(Ts)>([&](auto... Is){
        return std::make_tuple(std::get<Is>(bars).get_label(indices[Is])...);
    })
    

    And writing with in C++14 is straightforward. It's really just the same index_sequence trick, with an extra indirection (cause you need to then change the one index_sequence into the N integral_constants). Arguably, the result looks better too - it's less busy. I prefer this in C++20 anyway.