9

I've got a template function taking a parameter pack. I want to expand it into calls to a second function while also supplying the index of the item in the pack. I probably can work out how to do it with recursion but I would like to try do it with a fold expression.

This is the function I want the parameter pack to expand into

template<typename T>
void addToRecord(Record& rec, int idx, T&& val)
{
    // Do some stuff.
}

And this is the function that takes the parameter pack

template<typename... ARGS>
void addRecord(ARGS&& ...values)
{
    Record rec;
    // addToRecord(rec, ??????)  How do expand 'values' here addToRecord with index of each item?
}

Is this possible? I realize this isn't critical but I'm also trying to get better with using fold expressions.

Joe
  • 5,394
  • 3
  • 23
  • 54

3 Answers3

7

In addition to another answer let me mention a simpler approach that doesn't need a helper function:

template<typename... Args>
void addRecord(Args&&... values) {
    Record rec;

    int i = 0;
    (addToRecord(rec, i++, std::forward<Args>(values)), ...);
}

The comma operator , guarantees that all addToRecord()s will be called in order:

In a comma expression E1, E2, the expression E1 is evaluated, its result is discarded, and its side effects are completed before evaluation of the expression E2 begins.

Evg
  • 25,259
  • 5
  • 41
  • 83
  • I have to admit, this more like what I was hoping for. The difficulty I always encounter with fold expressions isn't so much understanding what happened after I see an example, it's realizing what I *can* do with them ahead of time. – Joe Sep 02 '21 at 13:23
  • 1
    @Joe You're not alone, I guess. ;) – Evg Sep 02 '21 at 13:59
  • +1 from me. Evg, is there any chance that you could provide a link to show that `foo` is always called before `bar` in `(foo(), bar())`? @Joe, any reason this isn't the accepted answer? – Elliott Sep 03 '21 at 08:07
  • 3
    @Elliott https://en.cppreference.com/w/cpp/language/operator_other#Built-in_comma_operator *The comma operator expressions have the form `E1, E2`. In a comma expression `E1, E2`, the expression `E1` is evaluated, its result is discarded, and its side effects are completed before evaluation of the expression `E2` begins.* – Evg Sep 03 '21 at 08:30
  • @Evg, perfect. Thanks! – Elliott Sep 03 '21 at 08:32
  • @Elliott I needed a day to finish putting together the rather large chunk of code I was writing to verify that it was correct. It was, of course. (It was obvious to me that it would be, but I've been wrong about "obvious" things too many times) – Joe Sep 03 '21 at 14:55
5

You can write a helper function that takes a compile time integer sequence, along with the values

template<typename... Args, std::size_t... Is>
void addRecord_impl(Record& rec, std::index_sequence<Is...>, Args && ...values)
{
    (addToRecord(rec, Is, std::forward<Args>(values)), ...);  // Is and values will be expanded in lock step
}

and then call this helper like this

template<typename... Args>
void addRecord(Args && ...values)
{
    Record rec;
    addRecord_impl(rec, 
                   std::index_sequence_for<Args...>{}, 
                   std::forward<Args>(values)...);
}

Here's a demo

cigien
  • 57,834
  • 11
  • 73
  • 112
  • Wow. That makes my C++98 brain explode. I will need some time to digest this. – Joe Sep 02 '21 at 02:10
  • @Joe Ouch, it's been a while since I haven't been able to use at least c++11. I'm pretty sure being made to write in 98 would make *my* head explode ;) – cigien Sep 02 '21 at 02:12
  • 1
    Oh no, I'm using C++17. I'm just old – Joe Sep 02 '21 at 03:45
  • 1
    @Joe Oh, I see what you mean. Yeah, the language has changed a lot. BTW, I was being a bit sloppy with not forwarding the arguments, so I fixed that. – cigien Sep 02 '21 at 03:58
  • error: expected primary-expression before ‘...’ token – Frank Puck May 13 '22 at 14:37
2

When I need to do this, I like doing it inline.

template<std::size_t...Is>
auto indexer_over( std::index_sequence<Is...> ) {
  return [](auto&& f)->decltype(auto){
    return f( std::integral_constant<std::size_t, Is>{}... );
  };
}
template<std::size_t N, class F>
auto index_upto( F&& f ) {
  return indexer_over(std::make_index_sequence<N>{})(std::forward<F>(f));
}

then your code is:

template<typename... ARGS>
void addRecord(ARGS&& ...values)
{
  Record rec;
  index_upto<sizeof...(ARGS)>( [&](auto...Is) {
    ( addToRecord(rec, Is), ... );
  } );
}

this has the advantage over @evg's solution that the Is are compile time constants for each call to addToRecord, if that matters.

In you can get the compile time collection of Is values without helpers.

[&]<std::size_t...Is>(std::index_sequence<Is...>) {
  ( addToRecord(rec,Is), ... );
}( std::make_index_sequence<sizeof...(ARGS)>{} );
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524