0

I have a tensor classes of rank N which wrap data stored in an array. For example, a rank-3 tensor would have dimensions (d0,d1,d2) and a unique element would be accessed with the multi-index (i0,i1,i2) from the underlying array of length d0*d1*d2. If d0=d1=d2=10, i0=1, i1=2, i2=3, then element 123 of the array would be accessed.

I've implemented a recursively defined class which computes single array index from the multi-index as follows:

template<size_t N>
class TensorIndex : TensorIndex<N-1> {
private:
  size_t d;
public:
template<typename...Ds>
TensorIndex( size_t d0, Ds...ds ) : TensorIndex<N-1>( ds... ), d(d0) {}
  template<typename...Is>
  size_t index( size_t i0, Is...is ) {
    return i0+d*TensorIndex<N-1>::index(is...);
  }
};

template<>
struct TensorIndex<1> {
TensorIndex( size_t ) {}
  size_t index( size_t i ) { return i; }
};

Which reverses the desired order.

TensorIndex<3> g(10,10,10);
std::cout << g.index(1,2,3) << std::endl;

outputs 321. What would be a simple way to reverse the order of the arguments for the constructor and index functions?

Edit: I tried implementing using the suggested approach of reversing the variadic arguments, but this was suboptimal as it required reversing the arguments for both index and the constructor and the necessary helper functions for these two cases would appear slightly different. The initializer list answer looks more straightforward.

Greg von Winckel
  • 2,261
  • 2
  • 16
  • 14
  • Possible duplicate of [How to reverse the order of arguments of a variadic template function?](https://stackoverflow.com/questions/15904288/how-to-reverse-the-order-of-arguments-of-a-variadic-template-function) –  Jul 27 '18 at 18:40

1 Answers1

1

No need of recursion nor to reverse, you can use initializer-list to call an evaluation function that accumulates index from left to right. The function object called in the initalizer-list should have a non-void return type :

#include <cstddef>
#include <iostream>

using namespace std;

template<size_t N>
class TensorIndex {
public:
    template<typename... Args>
    TensorIndex(Args... args) : dims{static_cast<size_t>(args)...}
    {
        static_assert(sizeof...(Args) == N,
                      "incorrect number of arguments for TensorIndex constructor");
    }

    template<typename... Args>
    size_t index(Args... args) {
        static_assert(sizeof...(Args) == N,
                      "incorrect number of arguments for TensorIndex::index()");
        IndexEval eval{dims};
        Pass pass{eval(args)...}; // evaluate from left to right : initializer-list                                                                           
        return eval.get_res();
    }

private:
    const size_t dims[N];

    class IndexEval {
        size_t k = 0;
        size_t res = 0;
        const size_t* dims;
    public:
        IndexEval(const size_t* dims) : dims{dims} {}
        size_t operator()(size_t i) {
            return res = res * dims[k++] + i;
        }
        size_t get_res() const { return res; }
    };

    struct Pass {
        template<typename... Args> Pass(Args...) {}
    };
};

int main()
{
    TensorIndex<3> g(10, 10, 10);
    cout << g.index(1, 2, 3) << endl;
}
Jérôme Migné
  • 244
  • 1
  • 5
  • This looks good. I implemented it reversing the variadic parameter pack using index_sequence and it was rather clunky as the order needed reverting for both index and dimension. – Greg von Winckel Jul 30 '18 at 22:26