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
myArraySize
according 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,
myArray
real code usually uses loops for
and / 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
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 detect what is myArraySize
not 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 myArray
with 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::array
there 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::array
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::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: