1

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?

Oliver Schönrock
  • 1,038
  • 6
  • 11
  • 2
    `Idxs` is a _non-type_ template parameter for the lambda – Mat Mar 23 '22 at 11:03
  • @Mat OK, yes. That's so true! I guess the confusing part for a moment was that that non-type parameter was actually being deduced based on the (pack) type of the ACTUAL parameter passed to the function?? ie the lambda is not being called with `<>` angle brackets, but instead the "type" of the "non-type template parameter", which is the sequence, is being deduced? – Oliver Schönrock Mar 23 '22 at 11:10

2 Answers2

1

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.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • Yes, and as Mat says above `Idxs` is a NON type template parameter pack, ie it's a set of compile time integer VALUES... not a set of types, as in many other "normal" parameter packs. – Oliver Schönrock Mar 23 '22 at 11:16
  • @OliverSchönrock yes `` is a pack of `std::size_t`s – 463035818_is_not_an_ai Mar 23 '22 at 11:27
  • @OliverSchönrock I understand that you are in the process of learning, but I recommend to not consider non-type arguments as "not normal". They are different but not less "normal". I am also not sure if they are that much less common – 463035818_is_not_an_ai Mar 23 '22 at 11:28
  • That's why I put "normal" in quotes ;-). I am quite familiar with NTTPs and use them often. I am less familiar with NTTP packs, and that's what caused the confusion here. It is a little baroque to pass a complex type (the sequence), then use a template to deduce this complex type in terms of a pack of NNTPs, which we then use for expansion... – Oliver Schönrock Mar 23 '22 at 11:32
  • @OliverSchönrock thats exactly what `index_sequence` is made for. See here for more details and examples: https://en.cppreference.com/w/cpp/utility/integer_sequence – 463035818_is_not_an_ai Mar 23 '22 at 11:45
  • Yes, well familiar with that page. Just not spent enough time with its usage... – Oliver Schönrock Mar 23 '22 at 11:50
1

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.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • Yes, that makes it clearer, thank you. And as Mat says in a comment above `Idxs` is a NON type template parameter pack, ie it's a set of compile time integer VALUES... not a set of types, as in many other "normal" parameter packs. So my "distraction" with "passing a type" is just incorrect... – Oliver Schönrock Mar 23 '22 at 11:20