Array class with negative indices for half of the items

I'm representing coordinates in a multi-dimensional array, and I'd like to stick as closely to native arrays as possible.

nArray.hpp

#include <stdexcept>

// Array that allows for negative integers
template <typename T, int SIZE> struct nArray {
private:
T array[SIZE];

public:
nArray() {
if ((SIZE + 1) % 2 != 0) {
throw std::length_error("Array size must be odd number");
}
}
T& operator[] (int index) {
if (abs(index) > (SIZE-1)/2) {
throw std::out_of_range("Array index is out of bounds");
}
return array[index + SIZE/2];
}
};


So something like nArray<nArray<int, 100>, 100> array; is a 2-dimensional array in which either bracket can accept any index whose absolute value is less than or equal to 50 without entering undefined-behavior territory (e.g. - array[-40][10]). As far as I can tell, it works as intended (specifically, things that crashed when I tried to give negative indices on a regular multi-dimensional array do not crash when I use negative indices on nArray), but I wouldn't be surprised if there is a lot of potentially dangerous stuff going on under-the-hood that I'm unaware of.

Does this look ok, or is this an example of how ignorance can destroy computers?

• This is insufficient code for a code review, we need to see how this is used in working code to be able to perform a proper code review. Mar 17, 2020 at 13:16
• @pacmaninbw The example I gave was array[-40][10] and that is all I am asking about; I think a "proper code review" would answer my question, which doesn't require anything outside of that example. Is my overloading of the [] operator sound, or are there native-array landmines I'm stepping on by doing this? Mar 17, 2020 at 13:27
• Out of curiosity, what's the use-case for this? Mar 17, 2020 at 19:20
• @John It's part of a C++ script representing a 3D coordinate system (I used a 2D example here for the sake of simplicity) where the origin of the map is (0, 0) (by the logic of the 3rd-party engine which uses the script). Also, I'm just picking up C++ again (after maybe a couple of weeks of trying it years ago), so the part about keeping it as close to native arrays as possible is more a learning exercise. Mar 17, 2020 at 19:25
• Thanks, that makes sense. Mar 17, 2020 at 19:26

The "odd" check in the constructor is a bit obfuscated, and it does not need to be executed at runtime. A simpler static_assert to check for odd (and non-negative) would report errors sooner.

std::static_assert(SIZE >= 0 && (SIZE & 1) == 1);


Having operator[] perform a range check is atypical. The operator[] is typically fast, with no error checking, while an at member function will perform range checks. However, your use case may require otherwise.

It won't work with const arrays, as there is no const version of the array access operator. constexpr versions can also do the range checking at compile time, if you're accessing elements using constants and not variables.

Future expansion

You can add in begin and end members (et. al.) for use with iteration and range based for loops.

Using std::array as the underlying storage type can make it a bit easier to implement.

This class could be modified/expanded to allow generic upper and lower bounds, rather than balanced ones.

Just few small points.

You can use std::array.

You can have the size represent half of the entire diameter. Thus having nArray<T, 50> allocate memory for indices -50 to 50 and avoid the odd size check.

You might also reconsider if you really need [][] access and whether it wouldnt be better to implement a class that has just 1d array to represent 2d matrix and offer access through a member method at(x, y). Or if you have a point like structure you could have something like operator[](const point & p).

Array size can hardly be negative, so std::size_t would be a better type for SIZE.

There's nothing wrong with negative indices as long as you stay inside the bounds. Maps and unordered maps can be indexed with values of arbitrary types (as long as those types satisfy requirements for keys), and that alone does not constitute a cause for segfault.