Array size constant antipattern

The translation of the article was prepared in anticipation 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 code above, the number five is repeated : first in the value myArraySize = 5, and 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, each 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 might update this code incorrectly :



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




The patch above looks like it changes the contents of the array from 2,7,1,8,2 to 3,1,4 , but it is not! In fact it changes it to 3,1,4,0,0 - with zeros padding - because the maintainer forgot to adjust myArraySizeaccording to myArray.



Reliable approach



Computers are pretty damn good at counting. 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 from 2,7,1,8,2 to 3,1,4 , by changing just one line of code. You don't need to duplicate the change anywhere.



Even more, myArrayreal code usually uses loops forand / or algorithms based on the iterator range to manipulate it , so it doesn'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'




The "bad" version of this code is myArraySizealways 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 detect what is myArraySizenot being used.



How to do this 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 short, for example, or an array uint32_t.



C ++ 20 gives us std :: to_array , which 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 moves (move-constructs) its elements into std::array. All of our previous examples were initialized myArraywith a curly-braced initializer list that triggered aggregate initialization and instantiated the array elements 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[]the newer std::array<T, N>.



In C ++ 11 and C ++ 14, std::arraythere was an ergonomic advantage in being able to say arr.size(); but that advantage evaporated when C ++ 17 provided us withstd::size(arr)and for inline arrays. There std::arrayare 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::list, unless you want the stability of its iterator, fast gluing, sorting without replacing elements, and so on. I am not saying 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 most 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:






All Articles