1

The title is bad but I couldn't come up with anything better. Feel free to change it.

Here's a template multidimensional array class that I'm currently working on. I'm trying to optimise it as much as I can:

#include <array>

template <typename T, std::size_t... Dimensions>
class multidimensional_array
{
    public:

        using value_type = T;
        using size_type = std::size_t;

    private:

        template<typename = void>
        static constexpr size_type multiply(void)
        {
            return 1u;
        }

        template<std::size_t First, std::size_t... Other>
        static constexpr size_type multiply(void)
        {
            return First * multidimensional_array::multiply<Other...>();
        }

    public:

        using container_type = std::array<value_type, multidimensional_array::multiply<Dimensions...>()>;
        using reference = value_type &;
        using const_reference = value_type const&;
        using iterator = typename container_type::iterator;

    private:

        container_type m_data_array;

        template<typename = void>
        static constexpr size_type linearise(void)
        {
            return 0u;
        }

        template<std::size_t First, std::size_t... Other>
        static constexpr size_type linearise(std::size_t index, std::size_t indexes...)
        {
            return multidimensional_array::multiply<Other...>()*index + multidimensional_array::linearise<Other...>(indexes);
        }

    public:

        // Constructor
        explicit multidimensional_array(const_reference value = value_type {})
        {
            multidimensional_array::fill(value);
        }

        // Accessors
        reference operator()(std::size_t indexes...)
        {
            return m_data_array[multidimensional_array::linearise<Dimensions...>(indexes)];
        }

        const_reference operator()(std::size_t indexes...) const
        {
            return m_data_array[multidimensional_array::linearise<Dimensions...>(indexes)];
        }

        // Iterators
        iterator begin()
        {
            return m_data_array.begin();
        }

        iterator end()
        {
            return m_data_array.end();
        }

        // Other
        void fill(const_reference value)
        {
            m_data_array.fill(value);
        }
};

My main function is

int main(void)
{
    multidimensional_array<int, 2u, 3u, 4u, 5u, 6u> foo;
    int k = 0;

    for (auto& s : foo)
        s = k++;

    //std::cout << foo(0u, 0u, 0u, 1u, 0u) << std::endl;
    return 0;
}

The above code compilers without warning/error. As soon as I uncomment the std::cout part though, I get this:

g++-7 -std=c++17 -o foo.o -c foo.cpp -Wall -Wextra -pedantic
foo.cpp: In instantiation of ‘multidimensional_array<T, Dimensions>::value_type& multidimensional_array<T, Dimensions>::operator()(std::size_t, ...) [with T = int; long unsigned int ...Dimensions = {2, 3, 4, 5, 6}; multidimensional_array<T, Dimensions>::reference = int&; multidimensional_array<T, Dimensions>::value_type = int; std::size_t = long unsigned int]’:
foo.cpp:99:37:   required from here
foo.cpp:60:72: error: no matching function for call to ‘multidimensional_array<int, 2, 3, 4, 5, 6>::linearise<2, 3, 4, 5, 6>(std::size_t&)’
    return m_data_array[multidimensional_array::linearise<Dimensions...>(indexes)];
                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~
foo.cpp:38:30: note: candidate: template<class> static constexpr multidimensional_array<T, Dimensions>::size_type multidimensional_array<T, Dimensions>::linearise() [with <template-parameter-2-1> = <template-parameter-1-1>; T = int; long unsigned int ...Dimensions = {2, 3, 4, 5, 6}]
   static constexpr size_type linearise(void)
                              ^~~~~~~~~
foo.cpp:38:30: note:   template argument deduction/substitution failed:
foo.cpp:60:72: error: wrong number of template arguments (5, should be at least 0)
    return m_data_array[multidimensional_array::linearise<Dimensions...>(indexes)];
                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~
foo.cpp:44:30: note: candidate: template<long unsigned int First, long unsigned int ...Other> static constexpr multidimensional_array<T, Dimensions>::size_type multidimensional_array<T, Dimensions>::linearise(std::size_t, std::size_t, ...) [with long unsigned int First = First; long unsigned int ...Other = {Other ...}; T = int; long unsigned int ...Dimensions = {2, 3, 4, 5, 6}]
   static constexpr size_type linearise(std::size_t index, std::size_t indexes...)
                              ^~~~~~~~~
foo.cpp:44:30: note:   template argument deduction/substitution failed:
foo.cpp:60:72: note:   candidate expects 2 arguments, 1 provided
    return m_data_array[multidimensional_array::linearise<Dimensions...>(indexes)];
                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~
Makefile:17: recipe for target 'foo.o' failed
make: *** [foo.o] Error 1

And I know why now. My question is, how can I fix linearise so that it can pass indexes without going through va_list and such? Unfortunately linearise is already a template, variadic function, so I can't use variadic template shenanigans in that regard.

max66
  • 65,235
  • 10
  • 71
  • 111
Pippin
  • 323
  • 1
  • 9
  • 1
    Not a bad question, but that's not a [mcve] -- that's not minimal. – user202729 May 12 '18 at 14:25
  • Ok, [possible duplicate](https://stackoverflow.com/questions/9831501/how-can-i-have-multiple-parameter-packs-in-a-variadic-template). – user202729 May 12 '18 at 14:26
  • 1
    @user202729 Looks like a reasonable question to me. Your behaviour looks like the worse problem of overzealous SO moderation. – Arthur Tacca May 12 '18 at 14:28
  • @ArthurTacca I doubt that the `operator()` and _its const variant_ and _swap/fill "utility"_ would be anyway useful. Besides, I hate scrolling to read code. – user202729 May 12 '18 at 14:28
  • I agree that you don't need to behave the way you do. Anyways, I've removed the swap function, but not the fill one since it's part of the constructor. My code is still very long, feel free to leave this thread for ever. – Pippin May 12 '18 at 14:32
  • 1
    @Pippin You don't need the `fill` function or the constructor either. Leaving it zero-initialize won't affect the functionality. – user202729 May 12 '18 at 14:34
  • @Pippin I have fixed my answer, in case you have only seen the old version. – Arthur Tacca May 12 '18 at 14:48

3 Answers3

3

As in preceding question the problem is that the following signatures

template<std::size_t First, std::size_t... Other>
static constexpr size_type linearise(std::size_t index,
                                     std::size_t indexes...)

reference operator()(std::size_t indexes...)

const_reference operator()(std::size_t indexes...) const

aren't what do you mean (indexes a variadic list of std::size_t) but are exactly equivalent to

template<std::size_t First, std::size_t... Other>
static constexpr size_type linearise(std::size_t index,
                                     std::size_t indexes,
                                     ...)

reference operator()(std::size_t indexes, ...)

const_reference operator()(std::size_t indexes, ...) const

where indexes is a single std::size_t followed by a C-style optional sequence of argument.

A simple solution (you tagged C++17 but is available starting from C++11) is based on the use of variadic templates.

By example, as follows

template <std::size_t First, std::size_t ... Other, typename ... Ts>
static constexpr size_type linearise (std::size_t index,
                                      Ts ... indexes)
 { return multidimensional_array::multiply<Other...>() * index
        + multidimensional_array::linearise<Other...>(indexes...); }

  // Accessors
  template <typename ... Ts>
  reference operator() (Ts ... indexes)
   { return m_data_array[
        multidimensional_array::linearise<Dimensions...>(indexes...)]; }

  template <typename ... Ts>
  const_reference operator() (Ts ... indexes) const
   { return m_data_array[
        multidimensional_array::linearise<Dimensions...>(indexes...)]; }

The following is you're code, modified and compilable

#include <array>
#include <iostream>

template <typename T, std::size_t ... Dimensions>
class multidimensional_array
 {
   public:
      using value_type = T;
      using size_type  = std::size_t;

   private:
      template <typename = void>
      static constexpr size_type multiply ()
       { return 1u; }

      template <std::size_t First, std::size_t ... Other>
      static constexpr size_type multiply(void)
       { return First * multidimensional_array::multiply<Other...>(); }

   public:
      using container_type  = std::array<value_type,
               multidimensional_array::multiply<Dimensions...>()>;
      using reference       = value_type &;
      using const_reference = value_type const &;
      using iterator        = typename container_type::iterator;

   private:
      container_type m_data_array;

      template <typename = void>
      static constexpr size_type linearise ()
       { return 0u; }

      template <std::size_t First, std::size_t ... Other, typename ... Ts>
      static constexpr size_type linearise (std::size_t index,
                                            Ts ... indexes)
       { return multidimensional_array::multiply<Other...>() * index
              + multidimensional_array::linearise<Other...>(indexes...); }

   public:
      // Constructor
      explicit multidimensional_array (const_reference value = value_type{})
       { multidimensional_array::fill(value); }

      // Accessors
      template <typename ... Ts>
      reference operator() (Ts ... indexes)
       { return m_data_array[
            multidimensional_array::linearise<Dimensions...>(indexes...)]; }

      template <typename ... Ts>
      const_reference operator() (Ts ... indexes) const
       { return m_data_array[
            multidimensional_array::linearise<Dimensions...>(indexes...)]; }

      // Iterators
      iterator begin ()
       { return m_data_array.begin(); }

      iterator end ()
       { return m_data_array.end(); }

      // Other
      void fill (const_reference value)
       { m_data_array.fill(value); }
 };

int main ()
 {
   multidimensional_array<int, 2u, 3u, 4u, 5u, 6u> foo;

   int k{ 0 };

   for ( auto & s : foo )
      s = k++;

   std::cout << foo(0u, 0u, 0u, 1u, 0u) << std::endl;
 }

Bonus suggestion.

You tagged C++17 so you can use "folding".

So you can substitute the couple of multiply() template functions

  template <typename = void>
  static constexpr size_type multiply ()
   { return 1u; }

  template <std::size_t First, std::size_t ... Other>
  static constexpr size_type multiply ()
   { return First * multidimensional_array::multiply<Other...>(); }

with a single folded one

  template <std::size_t ... Sizes>
  static constexpr size_type multiply ()
   { return ( 1U * ... * Sizes ); } 
max66
  • 65,235
  • 10
  • 71
  • 111
  • My code now compiles perfectly and works, thank you. I was not aware that it was possible to have more than 1 variadic template. What an important thing to remember. – Pippin May 12 '18 at 16:40
  • @Pippin - Yes: in some case you can have more than one variadic template list; but... is complicated. Anyway... added a bonus suggestion. – max66 May 12 '18 at 16:44
1

My approach is similar to that in this answer, except that instead of using std::tuple to store a list of types, I define my own type size_t_pack to store a (compile-time) list of size_t's.

using std::size_t;

template<size_t... values>
struct size_t_pack{};

template<size_t first_value,size_t... rest_values>
struct size_t_pack<first_value,rest_values...>{
    static constexpr size_t first=first_value;
    using rest=size_t_pack<rest_values...>;
    static constexpr size_t product=first*rest::product;
};

template<>struct size_t_pack<>{
    static constexpr size_t product=1;
};

Defines members: first, rest (in case not empty) and product (since it's not possible to specialize a function using the templates of a template argument, as far as I know, another choice is to if constexpr and make the type support checking for empty)

With that, it's easy to define the linearize function:

template<class dimensions,class... SizeTs>
static constexpr size_type linearise(std::size_t index, SizeTs... indices)
{
    using restDimensions=typename dimensions::rest;
    return restDimensions::product *index + 
    multidimensional_array::linearise<restDimensions>(indices...);
}

Using a std::tuple to store the list of types (SizeTs) is also possible, although struct partial specialization is still required, as far as I know.

user202729
  • 3,358
  • 3
  • 25
  • 36
0

You need to make indexes a parameter pack by making the operator() function a template, and expand the parameter pack when you use it by putting ... afterwards:

    template <class... DimensionType>
    const_reference operator()(DimensionType... indexes) const
    {
        return m_data_array[multidimensional_array::linearise<Dimensions...>(indexes...)];
    }

See: parameter pack expansion

The code still will not compile because of a similar problem in linearize(), but that gets you on the right track.

Arthur Tacca
  • 8,833
  • 2
  • 31
  • 49