Expand parameter pack with index using a fold expression

Viewed 573

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.

3 Answers

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

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.

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)>{} );
Related