17
\$\begingroup\$

A interesting challenge was brought to my attention: to "flip" an array. I thought it might be more convenient if its bounds were symmetrical, say [-N..N] instead of [0 .. 2N + 1].

Now I'm curious about what could be done to make this Array more mature. Say, like a Boost component.

#include <cassert>
#include <iostream>
#include <algorithm>

template <typename T, int LoBound, int HiBound> class Array {
  T arr_[HiBound - LoBound + 1];
public:
  Array()
  {
    std::fill(arr_, arr_ + sizeof(arr_) / sizeof(arr_[0]), T{});
  }
  Array(const Array& other)
    :arr_(other.arr_)
  {}
  Array(std::initializer_list<T> initList)
  {
    std::copy(initList.begin(), initList.end(), arr_);
  }
  T& operator[](int ix)
  {
    assert(ix >= LoBound && ix <= HiBound);
    return arr_[ix - LoBound];
  }
  const T& operator[](int ix) const
  {
    assert(ix >= LoBound && ix <= HiBound);
    return arr_[ix - LoBound];
  }
};

int main()
{
  enum {LoBound = -2, HiBound = 2};
  using A1D = Array<int, LoBound, HiBound>;
  Array<A1D, LoBound, HiBound> arr =
   { A1D{ 1,  2,  3,  4,  5},
     A1D{ 6,  7,  8,  9, 10},
    A1D{11, 12, 13, 14, 15},
    A1D{16, 17, 18, 19, 20},
    A1D{21, 22, 23, 24, 25} };
  for (int i = LoBound; i <= HiBound; ++i) {
    for (int j = LoBound; j <= HiBound; ++j) {
      std::cout << arr[-i][-j] << " ";
    }
    std::cout << "\n";
  }
  return 0;
}
\$\endgroup\$
10
\$\begingroup\$

It looks pretty clean to me.

  • The copy constructor doesn't have to be explicitly declared, you could = default it or just omit.

  • You shouldn't fill() the array. It is already default constructed, so that's just duplicated work. The default constructor again could be = default or omitted.

  • Actually, maybe could provide an explicit constructor that takes a fill value. That can be handy.

  • A size() method to return sizeof(arr_) / sizeof(arr_[0]) should be useful, for both internal and external uses.

  • Mark the class final? I'd probably do it, but that's probably a matter of personal preference.

  • If you're interested in making this class Standard compliant, well, there's a long road ahead ;). It needs iterators and several other methods ([c]begin/[c]end/front/back/at...). Look into std::array for the details.

\$\endgroup\$
10
\$\begingroup\$

Write Less Code

The main problem I see with this solution is that you're writing so much more code than you need to to accomplish the task at hand.

  1. First of all, your copy constructor is broken since you can't use an array as an initializer. But really, you don't need it at all. The default copy constructor is fine.

    Array(const Array&) = default;
    
  2. Your default constructor value-initializes your elements but you could accomplish the same without needing to write it by providing a brace-or-equal initializer:

    T arr_[HiBound - LoBound + 1]{};
    
    Array() = default;
    
  3. You don't even need the initializer_list<> constructor - you could make your class an aggregate by making arr_ public. This is what std::array does.

This all halves the amount of code we need to write.

Argument Ordering

Maybe flip HiBound and LoBound and default LoBound to 0. Also potentially make HiBound an exclusive bound - so Array<int, 10> becomes the same as std::array<int, 10>, and Array<int, 10, -10> supports the indices -10, -9, ..., 9. That has a nice symmetry to it. YMMV.

More functions

I'd go ahead and implement all the other member functions from std::array too.

Run-on lines

Write the template arguments on a separate line the thing they're declaring:

template <class T, int LoBound, int HiBound>
class Array

makes it easier to read.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.