Шаблон многомерного массива с вычетом размерности
Я хочу сделать 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
иi
th элемент инициализатора в качестве соответствующего аргумента. в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