Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multi-dimension array template with dimension deduction

I want to make an NDArray template which has a fixed dimension, but can be resized across each dimension.

My question is how to make it be able to deduce the dimensions in the constructor according to how many pair of {} is used? The elements in the constructor will be used to initialize some of the elements.

#include <array>
#include <iostream>

template<typename T, size_t Dimension>
class NDArray
{
    T* buffer = nullptr; //flattened buffer for cache locality
    std::array<size_t, Dimension> dimension;    //keep the current sizes of each dimension
public:
    NDArray(std::initializer_list<T> elements) : dimension{elements.size()}   //for 1D
    {
        std::cout << "Dimension = " << Dimension << '\n';
    }
    NDArray(std::initializer_list<NDArray<T, Dimension-1>> list) //how to make this works???
    {
        std::cout << "Dimension = " << Dimension << '\n';
    }
};

template<typename T, size_t N>
NDArray(const T(&)[N]) -> NDArray<T, 1>;

int main()
{
    NDArray a{ {3,4,5} };//OK, NDArray<int, 1> because of the deduction guide
    NDArray b{ {{1,2,3}, {4,5,6}} };//Nope, I want: NDArray<int, 2>
}
like image 652
sz ppeter Avatar asked Sep 01 '20 21:09

sz ppeter


People also ask

How to declare a multi-dimensional array in C?

Declaring a multi-dimensional array is similar to the one-dimensional arrays. For a 2D array, we need to tell C that we have 2 dimensions. This example declares a 2D integer array: A valid type is required (in this case int), followed by the name of the array, and the size of each dimension.

How to calculate total number of elements in a multidimensional array?

Total number of elements that can be stored in a multidimensional array can be calculated by multiplying the size of all the dimensions. For example: The array int x[10][20] can store total (10*20) = 200 elements. Similarly array int x[5][10][20] can store total (5*10*20) = 1000 elements.

What is a two dimensional array example?

Two – dimensional array is the simplest form of a multidimensional array. A two – dimensional array can be seen as an array of one – dimensional array for easier understanding. data_type [] [] array_name = new data_type [x] [y]; For example: int [] [] arr = new int [10] [20];

How to iterate through all the elements of the multidimensional array?

To iterate through all the elements of the multidimensional array we need to use nested for loop concept as below: NOTE: The inner arrays of the multidimensional array can be of variable length which makes it different from other programming languages multidimensional array where the length or columns are fixed.


Video Answer


2 Answers

This is impossible in the general case, but possible in however many specific cases you want to spell out.

An initializer list has no type. The only way you can deduce a type for it (as in, separate from having a default template argument) is that we have two special cases spelled out in [temp.deduct.call]/1:

If removing references and cv-qualifiers from P gives std::initializer_list<P′> or P′[N] for some P′ and N and the argument is a non-empty initializer list ([dcl.init.list]), then deduction is performed instead for each element of the initializer list independently, taking P′ as separate function template parameter types P′i and the ith initializer element as the corresponding argument. In the P′[N] case, if N is a non-type template parameter, N is deduced from the length of the initializer list. Otherwise, an initializer list argument causes the parameter to be considered a non-deduced context ([temp.deduct.type]).

This is the rule that lets the following work:

template <typename T>
constexpr auto f(std::initializer_list<T>) -> int { return 1; }

static_assert(f({1, 2, 3}) == 1);

But that isn't enough to get this to work:

static_assert(f({{1, 2}, {3, 4}}) == 1); // ill-formed (no matching call to f)

Because the rule is - okay, we can strip one layer of initializer_list but then we have to deduce the elements. And once we strip one layer of initializer list, we're trying to deduce T from {1, 2} and that fails - we can't do that.

But we know how to deduce something from {1, 2} - that's this same rule. We just have to do it again:

template <typename T>
constexpr auto f(std::initializer_list<T>) -> int { return 1; }

template <typename T>
constexpr auto f(std::initializer_list<std::initializer_list<T>>) { return 2; }


static_assert(f({1, 2, 3}) == 1);
static_assert(f({{1, 2}, {3, 4}}) == 2);

and again:

template <typename T>
constexpr auto f(std::initializer_list<T>) -> int { return 1; }

template <typename T>
constexpr auto f(std::initializer_list<std::initializer_list<T>>) { return 2; }

template <typename T>
constexpr auto f(std::initializer_list<std::initializer_list<std::initializer_list<T>>>) { return 3; }


static_assert(f({1, 2, 3}) == 1);
static_assert(f({{1, 2}, {3, 4}}) == 2);
static_assert(f({{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}) == 3);

The same way we have the carve-out for std::initializer_list<T>, we also have the carve-out for T[N]. That works the same way, just a bit less typing:

template <typename T, size_t N>
constexpr auto f(T(&&)[N]) -> int { return 1; }

template <typename T, size_t N1, size_t N2>
constexpr auto f(T(&&)[N1][N2]) { return 2; }

template <typename T, size_t N1, size_t N2, size_t N3>
constexpr auto f(T(&&)[N1][N2][N3]) { return 3; }


static_assert(f({1, 2, 3}) == 1);
static_assert(f({{1, 2}, {3, 4}}) == 2);
static_assert(f({{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}) == 3);
like image 77
Barry Avatar answered Oct 22 '22 10:10

Barry


You may achieve almost what you want, if you are OK with explicitly creating NDArray inside the std::initialize_list this way:

int main()
{
    NDArray a{3,4,5}; // would be deduced to NDArray<int, 1>
    NDArray b{ NDArray{1,2,3}, {4,5,6} }; // would be deduced to NDArray<int, 2>
}

Note that it is enough to explicitly add NDArray only for the first appearance of each dimension. For example, for 3D NDArray:

NDArray c { NDArray{ NDArray{8, 3}, {1, 2}, {1, 2, 3} }, 
                   {        {4, 5}, {8, 9, 7}, {2, 5} } };

To achieve that you need to have these two deduction guides:

template<typename T>
NDArray(const std::initializer_list<T>&)
                -> NDArray<T, 1>;

template<typename T, size_t DIMENSIONS>
NDArray(const std::initializer_list<NDArray<T, DIMENSIONS>>&) 
                -> NDArray<T, DIMENSIONS + 1>;

Code example: http://coliru.stacked-crooked.com/a/1a96b1eaa0717a67

like image 3
Amir Kirsh Avatar answered Oct 22 '22 09:10

Amir Kirsh