Oliver Schönrock
Oliver Schönrock

Reputation: 1156

Enumerating a pack

I don't quite understand the base trick from Daisy Hollman's talk:

https://youtu.be/15etE6WcvBY?t=2670

Enumerating a pack using c++20 lamdas with explicit template arguments.

#include <iostream>
#include <utility>

template <typename Function, typename ...Ts>
void enumerate_pack(Function f, Ts... args) {
  [&]<std::size_t... Idxs>(std::index_sequence<Idxs...>) { (f(Idxs, args), ...); }
  (std::make_index_sequence<sizeof...(args)>{});
}

int main() {
  enumerate_pack([](std::size_t i, auto arg) { std::cout << i << ": " << arg << "\n"; }, "hello",
                 42, "world", 73.2);
}

My problem with it is this part:

  (f(Idxs, args), ...);

This looks to me like we are "passing a type to a function". OK, we are expanding the pack so it's a single type and not the whole pack, but it's still a "type", to my eyes. And even more confusingly, the second argument args is the name of a local variable - again a pack, but a variable nonetheless. The usage of args makes much more sense to me.

I would have thought the syntax should be:

  [&]<std::size_t... Idxs>(std::index_sequence<Idxs...> idxs) { (f(idxs, args), ...); }

Note that I have now given a name to the parameter pack (idxs) and used that. This doesn't compile.

Is the syntax here just a little baroque, or is there something deeper I am missing?

Upvotes: 1

Views: 109

Answers (2)

Sam Varshavchik
Sam Varshavchik

Reputation: 118415

The lambda is being called with a single parameter:

(std::make_index_sequence<sizeof...(args)>{})

If you open the documentation for std::make_index_sequence, it will explain that it produces a std::index_sequence<0, 1, 2, 3>, for example, if sizeof...(args) is 4.

So, in this example, the parameter to the lambda is a std::index_sequence<0, 1, 2, 3>.

The lambda declaration uses a template to deduce std::size_t... Idxs as 0, 1, 2, 3, and each one of these get fed, one at a time, to f() as its first parameter. The call to f() simultaneously expands two parameter packs, the parameters to these functions, and the one that's deduced from the call to the lambda.

Upvotes: 1

463035818_is_not_an_ai
463035818_is_not_an_ai

Reputation: 122657

This

f(Idxs, args), ...

Because both Idxs and args are parameter packs, expands to:

f(0, args0), f(1,args1), .....

If you replace Idxs with a std::index_sequence<Idxs...> then it isnt a pack, it does not expand and f(std::index_sequence<Idxs...>, argX) is the wrong signature for f. There is no overload for f that takes a std::index_sequence<Idxs...> as first paramter.

The only purpose of the index sequence is to get your hands on the indices. The index sequence itself is not used, hence needs not be named.

Upvotes: 1

Related Questions