Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c++17 efficiently multiply parameter pack arguments with std::array elements

I want to efficiently multiply the arguments from a parameter pack with the elements of a std::array:

int index(auto... Is, std::array<int,sizeof...(Is)> strides)
{
  // pseudo-code
  // int idx = 0;
  // for(int i = 0; i < sizeof...(Is); ++i)
  //   idx += Is[i] * strides[i];
  // return idx; 
}

I can't quite wrap my brain around this one. I started down the road of an index sequence, but I could figure out how to incorporate the summation.

I am using c++17, so fold expressions are fair game if they would simplify the code.

Thanks for any pointers.

EDIT: Clarified the pseudo-code. The only pseudo part is the expression Is[i] which refers to the i'th parameter pack argument.

T.C.'s answer below was perfect and here is my final code which is a member function:

unsigned int index(auto... indexes)
{
    unsigned int idx = 0, i = 0;
    (..., (idx += indexes * m_strides[i++]));
    return idx;
}

As of this writing, the code compiles using gcc 6.3.0 with the -fconcepts flag, which brings in the Concept TS.

Using auto... indexes is shorthand for template<typename Args> f(Args... indexes). I tried to use an unsigned int concept for the arguments, but I couldn't get that to work.

The (...,) fold is the key element and expands to something like (if you could actually [] into the parameter pack):

idx += indexes[0] * m_strides[i++], idx += indexes[1] * m_strides[i++], etc.

That was the insight I was missing.

like image 653
RandomBits Avatar asked Mar 17 '17 05:03

RandomBits


2 Answers

I can't get auto... to work, so I changed the signature of index.

You will need an auxiliary function (index_helper here) to use index_sequence, since it relies on template argument deduction to fill in the indices.

#include <array>
#include <cstdio>

template <typename... T, size_t... i>
//                       ^~~~~~~~~~~
//                        use deduction to make {i...} = {0, 1, 2, ..., n}
static int index_helper(const std::array<int, sizeof...(T)>& strides,
                        std::index_sequence<i...>,
                        T... Is) 
{
    return (0 + ... + (strides[i] * Is));
}

template <typename... T>
int index(const std::array<int, sizeof...(T)>& strides, T... Is) {
    return index_helper(strides, std::make_index_sequence<sizeof...(T)>(), Is...);
//                               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//                                generates {0, 1, 2, ..., n}
}

int main() {
    printf("%d\n", index({1, 100, 100000, 1000}, 2, 3, 5, 7));
    // 507302
}
like image 113
kennytm Avatar answered Jan 02 '23 07:01

kennytm


If you can hammer down the argument pack into one single type that is cheap to copy/move, you can just make it into an array:

T arr[] = { static_cast<T>(Is)... }; // for some T, possibly common_type_t<decltype(Is)...>

Then you can just turn your pseudocode into real code.

If that's not feasible, a comma fold can be used:

int idx = 0, i = 0;
(..., (idx += Is * strides[i++]));
return idx;
like image 24
T.C. Avatar answered Jan 02 '23 07:01

T.C.