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>
}
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:
This is the rule that lets the following work:
But that isn't enough to get this to work:
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 deduceT
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:and again:
The same way we have the carve-out for
std::initializer_list<T>
, we also have the carve-out forT[N]
. That works the same way, just a bit less typing: