I have an N-dimensional vector class ndvector with the following constructors (definitions excluded):
template <typename T> class ndvector {
public:
constexpr ndvector(std::initializer_list<T> il);
constexpr ndvector(std::initializer_list<ndvector<T>> il);
};
These constructors allow for the initialization of ndvectors of any dimension:
ndvector a{1, 2};
ndvector b{a, {3, 4}};
ndvector<int> c{{1, 2}, {3, 4}};
ndvector d{b, {a, {3, 4}}};
ndvector<int> e{{{1, 2}, {3, 4}}, {{1, 2}, {3, 4}}};
For a reason I don't fully understand, T must be explicitly specified in some cases, but that is a small price to pay.
That being said, I ran into some issues while attempting to achieve similar functionality with a statically-allocated ndarray class:
template <typename T, std::size_t... DIMS> class ndarray;
template <typename T, std::size_t DIM, std::size_t... DIMS>
class ndarray<T, DIM, DIMS...> {
public:
template <typename... Ts>
constexpr ndarray(Ts &&...elements);
template <std::convertible_to<ndarray<T, DIMS...>>... Ts>
constexpr ndarray(Ts &&...elements);
};
template <typename T, std::same_as<T>... Ts>
ndarray(T, Ts...) -> ndarray<T, 1 + sizeof...(Ts)>;
template <typename T, std::same_as<T>... Ts, std::size_t... DIMS>
ndarray(ndarray<T, DIMS...>, ndarray<Ts, DIMS...>...)
-> ndarray<T, 1 + sizeof...(Ts), DIMS...>;
Even with explicit template arguments, the last line in the snippet below fails to compile:
ndarray a{1, 2};
ndarray<int, 2> b{3, 4};
ndarray c{a, b};
ndarray<int, 2, 2> d{{1, 2}, {3, 4}};
(Note: at the time of writing, MSVC will encounter an internal compiler error for any instantiation of ndarray due to a bug. I am using GCC 11, but GCC 10 and various versions of Clang also work).
As far as I am aware, this is because {1, 2}, etc. are treated as initializer lists that cannot be resolved to a type. Unfortunately, I cannot use std::initializer_list because they rarely form constant expressions, which would mean that DIMS could never be determined implicitly. I have also tried the following two constructors, to no avail:
template <typename... Ts> requires (... || !std::convertible_to<Ts, ndarray<T, DIMS...>>)
constexpr ndarray(Ts &&...elements);
template<typename... Ts>
constexpr ndarray(const ndarray<Ts, DIMS...> &...elements) {}
constexpr ndarray(const ndarray<T, DIMS...> elements...) {}
The first alternative fails for the same reason - Ts cannot be deduced from the untyped initializer list. C-style variadic arguments don't work either, as the first initializer list will seemingly attempt to initialize elements. Is there another approach that could bear more fruit?