0

I have an N-dimensional array class with two template parameters, a class and a size_t. To construct a 3-dimensional array of size x by y by z, I would like to be able to use:

ArrayND<float> A(x, y, z);

...but can't figure out how to do so--as it stands, I have to redundantly specify the dimension:

ArrayND<float, 3> A(x, y, z);

I thought that this deduction guide:

template <class T, class ...I>
ArrayND(I...) -> ArrayND<T, sizeof...(I)>;

...would be sufficient to allow the omission of the dimension parameter, but it isn't--when I try to compile a program containing "ArrayND<float> A(x, y, z)" with G++ 12.1.0 using -std=c++20, it fails ("error: wrong number of template arguments (1, should be 2)"). Is it possible to do what I'm trying to? If so, what am I doing wrong?

A simplified version of the class in question:

template <class T, size_t N>
class ArrayND {
    // Members: the actual storage, and the conceptual size
    std::vector<T> storage;
    size_t dims[N];

public:
    // Constructor: ArrayND<T, N>(dim1, dim2, ...);
    template <class ...I> requires (sizeof...(I) == N && std::is_integral_v<std::common_type_t<I...>>)
    constexpr ArrayND(const I ...i): storage((i * ...)), dims{i...} {}

private:
    // Indexing helper--convert from (i, j, k...) to the actual index within storage
    template <decltype(N) M=N-1, class I, class ...J>
    constexpr const auto flattened_index(const I i, const J ...j) const {
        auto index = i * std::accumulate(dims+N-M, dims+N, 1, std::multiplies());
        if constexpr (sizeof...(J) > 0) index += flattened_index<M-1>(j...);
        return index;
    }

public:
    // Index a cell
    template <class ...I> requires (sizeof...(I) == N && std::is_integral_v<std::common_type_t<I...>>)
    constexpr auto &operator()(const I ...i) { return storage[flattened_index(i...)]; }

    // Access underlying data
    constexpr auto data() { return storage.data(); }
};

A minimal working example can be found here; delete the 3 on line 47 to see the failure.

  • 2
    [CTAD](https://en.cppreference.com/w/cpp/language/class_template_argument_deduction) is a "all or nothing". You might do a `makeArrayND(x, y, z);` though. – Jarod42 Sep 21 '22 at 17:16
  • 1
    Alternatively `template struct Tag { }; template constexpr Tag Type; ... template ArrayND(Tag, I...) ->ArrayND; ArrayND A(Type, 1u, 2u, 3u);` with an overload of the constructor defined. – fabian Sep 21 '22 at 17:40
  • [fabian](https://stackoverflow.com/users/2991525/fabian) I like that too; sad that it's so clunky to get there. – Michael Greenburg Sep 21 '22 at 19:15
  • It seems strange that [Jarod42](https://stackoverflow.com/users/2684539/jarod42)'s suggestion works (`template auto makeArrayND(const I ...i) { return ArrayND(i...); }`), but the similarly-structured deduction guide doesn't. I guess there's hope for C++23? – Michael Greenburg Sep 21 '22 at 19:25
  • It is on purpose. So I doubt then change of mind. – Jarod42 Sep 22 '22 at 10:06

0 Answers0