Шаблон многомерного массива с вычетом размерности

Я хочу сделать NDArray шаблон с фиксированным размером, но его размер можно изменять по каждому измерению.

Мой вопрос в том, как заставить его определять размеры в конструкторе в зависимости от того, сколько пар {}используется? Элементы в конструкторе будут использоваться для инициализации некоторых элементов.

#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>
}

2 ответа

Это невозможно в общем случае, но возможно во многих конкретных случаях, которые вы хотите указать.

Список инициализаторов не имеет типа. Единственный способ вывести для него тип (например, отдельно от аргумента шаблона по умолчанию) - это то, что у нас есть два особых случая, прописанных в [temp.deduct.call] / 1:

Если удалить ссылки и cv-квалификаторы из P дает std::initializer_list<P′>или P′[N] для некоторых P′ а также N а аргументом является непустой список инициализаторов ([dcl.init.list]), тогда вместо этого выполняется вывод для каждого элемента списка инициализаторов независимо, принимая P′ как отдельные типы параметров шаблона функции P′i и ith элемент инициализатора в качестве соответствующего аргумента. вP′[N] случае, если N - не типовой параметр шаблона, Nвыводится из длины списка инициализаторов. В противном случае аргумент списка инициализаторов заставляет параметр считаться невыявленным контекстом ([temp.deduct.type]).

Это правило позволяет работать следующим образом:

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

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

Но этого недостаточно, чтобы это работало:

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

Потому что правило - хорошо, мы можем снять один слой initializer_listно тогда мы должны вывести элементы. И как только мы удалим один слой из списка инициализаторов, мы попытаемся вывестиT из {1, 2} и это не удается - мы не можем этого сделать.

Но мы знаем, как что-то вывести из{1, 2}- это то же самое правило. Нам просто нужно сделать это снова:

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);

и опять:

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);

Таким же образом у нас есть вырезка для std::initializer_list<T>, у нас также есть вырезка для T[N]. Это работает точно так же, только набирать немного меньше:

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);

Вы можете добиться почти того, чего хотите, если у вас все в порядке с явным созданием NDArray внутриstd::initialize_list сюда:

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>
}

Обратите внимание, что достаточно явно добавить NDArray только для первого появления каждого измерения. Например, для 3D NDArray:

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

Для этого вам необходимо иметь эти два руководства по умениям:

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>;

Пример кода: http://coliru.stacked-crooked.com/a/1a96b1eaa0717a67

Другие вопросы по тегам