Array size constant antipattern

Translation of the article was prepared on the eve of the start of the course “C ++ Developer. Professional “


I want to draw your attention to the anti-pattern, which I often come across in the code of students on the Code Review StackExchange and even in a fairly large number of educational materials (!) Of other people. They have an array of, say, 5 elements; and then, since magic numbers are bad, they introduce a named constant to represent the cardinality of “5”.

void example()
{
    constexpr int myArraySize = 5;
    int myArray[myArraySize] = {2, 7, 1, 8, 2};
    ...

But the solution is so-so! In the above code, the number is five repeats: first in value myArraySize = 5and then again when you actually assign the elements myArray… The above code is just as terrible from a maintenance standpoint as:

constexpr int messageLength = 45;
const char message[messageLength] =
    "Invalid input. Please enter a valid number.n";

– which, of course, none of us will ever write.

Repeated code is not good

Note that in both of the above code snippets, every time you change the contents of the array or the wording of the message, you must update two lines of code instead of one. Here’s an example of how a maintainer can update this code. wrong:

   constexpr int myArraySize = 5;
-   int myArray[myArraySize] = {2, 7, 1, 8, 2};
+   int myArray[myArraySize] = {3, 1, 4};

Patch above looks like as if it modifies the contents of the array with 2,7,1,8,2 on the 3,1,4but it is not! In fact, he changes it to 3,1,4,0,0 – padded with zeros – because the maintainer forgot to adjust myArraySize in accordance with myArray

Reliable approach

As for counting, computers are pretty damn good at it. So let the computer count!

int myArray[] = {2, 7, 1, 8, 2};
constexpr int myArraySize = std::size(myArray);

Now you can change the contents of the array, say with 2,7,1,8,2 on the 3,1,4by changing just one line of code. You don’t need to duplicate the change anywhere.

Even more, to manipulate myArray real code usually uses loops for and / or algorithms based on the range of the iterator, so it won’t need a named variable at all to store the size of the array.

for (int elt : myArray) {
    use(elt);
}
std::sort(myArray.begin(), myArray.end());
std::ranges::sort(myArray);

// Warning: Unused variable 'myArraySize'

In the “bad” version of this code myArraySize always used (in the declaration myArray), and therefore the programmer is unlikely to see that it can be excluded. In the “good” version, it is easy for the compiler to find that myArraySize not used.

How to do it with std::array?

Sometimes a programmer takes another step towards the Dark Side and writes:

constexpr int myArraySize = 5;
std::array<int, myArraySize> myArray = {2, 7, 1, 8, 2};

This should be rewritten as at least:

std::array<int, 5> myArray = {2, 7, 1, 8, 2};
constexpr int myArraySize = myArray.size();  // или std::size(myArray)

However, there is no easy way to get rid of the manual counting on the first line. CTAD C ++ 17 lets you write

std::array myArray = {2, 7, 1, 8, 2};

but this only works if you need an array int – it won’t work if you need an array shortfor example or an array uint32_t

C ++ 20 gives us std :: to_arraywhich allows us to write

auto myArray = std::to_array<int>({2, 7, 1, 8, 2});
constexpr int myArraySize = myArray.size();

Note that this creates a C array and then move (move-constructs) its elements in std::array… All of our previous examples have initialized myArray using a curly-braced initializer list that triggered aggregate initialization and instantiated the array elements directly in place.

In any case, all of these options result in a large number of additional template instances compared to good old C arrays (which don’t require template instantiation). Therefore, I strongly prefer T[] newer std::array<T, N>

In C ++ 11 and C ++ 14, std::array had the ergonomic advantage of being able to say arr.size(); but that advantage evaporated when C ++ 17 provided us with std::size(arr) and for inline arrays. Have std::array there are no more ergonomic advantages. Use it if you need its whole object variable semantics (pass the entire array to a function! Return an array from a function! Assign arrays with =! Compare arrays with ==!), But otherwise I recommend avoiding using std::array

Likewise, I recommend avoiding std::listif you don’t need the stability of its iterator, fast gluing, sorting without replacing elements, etc. I’m not saying that there is no place for these types in C ++; I’m just saying that they have a “very specific set of skills,” and if you don’t use those skills, you are likely overpaying.

Conclusions: do not fence the cart in front of the horse. In fact, the cart may not even be needed. And if you need to use the zebra to do the horse’s work, you also shouldn’t fence the cart in front of the zebra.

Read more:

  • Examples of C ++ code before and after Ranges

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *