Search

6.15 — An introduction to std::array

In previous lessons, we’ve talked at length about fixed and dynamic arrays. Although both are built right into the C++ language, they both have downsides: Fixed arrays decay into pointers, losing the array length information when they do, and dynamic arrays have messy deallocation issues and are challenging to resize without error.

To address these issues, the C++ standard library includes functionality that makes array management easier, std::array and std::vector. We’ll examine std::array in this lesson, and std::vector in the next.

An introduction to std::array in C++11

Introduced in C++11, std::array provides fixed array functionality that won’t decay when passed into a function. std::array is defined in the array header, inside the std namespace.

Declaring a std::array variable is easy:

Just like the native implementation of fixed arrays, the length of a std::array must be set at compile time.

std::array can be initialized using an initializer lists or uniform initialization:

Unlike built-in fixed arrays, with std::array you can not omit the array length when providing an initializer:

You can also assign values to the array using an initializer list

Accessing std::array values using the subscript operator works just like you would expect:

Just like built-in fixed arrays, the subscript operator does not do any bounds-checking. If an invalid index is provided, bad things will probably happen.

std::array supports a second form of array element access (the at() function) that does bounds checking:

In the above example, the call to array.at(1) checks to ensure array element 1 is valid, and because it is, it returns a reference to array element 1. We then assign the value of 6 to this. However, the call to array.at(9) fails because array element 9 is out of bounds for the array. Instead of returning a reference, the at() function throws an error that terminates the program (note: It’s actually throwing an exception of type std::out_of_range -- we cover exceptions in chapter 15). Because it does bounds checking, at() is slower (but safer) than operator[].

std::array will clean up after itself when it goes out of scope, so there’s no need to do any kind of cleanup.

Size and sorting

The size() function can be used to retrieve the length of the std::array:

This prints:

length: 5

Because std::array doesn’t decay to a pointer when passed to a function, the size() function will work even if you call it from within a function:

This also prints:

length: 5

Note that the standard library uses the term “size” to mean the array length — do not get this confused with the results of sizeof() on a native fixed array, which returns the actual size of the array in memory (the size of an element multiplied by the array length). Yes, this nomenclature is inconsistent.

Also note that we passed std::array by (const) reference. This is to prevent the compiler from making a copy of the std::array when the std::array was passed to the function (for performance reasons).

Rule: Always pass std::array by reference or const reference

Because the length is always known, for-each (ranged for) loops work with std::array:

You can sort std::array using std::sort, which lives in the algorithm header:

This prints:

1 3 5 7 9

The sorting function uses iterators, which is a concept we haven’t covered yet, so for now you can treat the parameters to std::sort() as a bit of magic. We’ll explain them in the lesson on iterators.

Manually indexing std::array via size_type

Pop quiz: What’s wrong with the following code?

The answer is that there’s a likely signed/unsigned mismatch in this code! Due to a curious decision, the size() function and array index parameter to operator[] use a type called size_type, which is defined by the C++ standard as an unsigned integral type. Our loop counter/index (variable i) is a signed int. Therefore both the comparison i < myArray.size() and the array index myArray[i] have type mismatches.

Interestingly enough, size_type isn't a global type (like int or std::size_t). Rather, it's defined inside the definition of std::array (C++ allows nested types). This means when we want to use size_type, we have to prefix it with the full array type (think of std::array acting as a namespace in this regard). In our above example, the fully-prefixed type of "size_type" is std::array<int, 5>::size_type!

Therefore, the correct way to write the above code is as follows:

That's not very readable. A type alias can help a bit:

This is a bit better, and this solution probably has the best combination of technical correctness and readability.

In all implementations of std::array, size_type is a typedef for std::size_t. So it's somewhat common to see developers use size_t instead for brevity:

A better solution is to avoid manual indexing of std::array in the first place. Instead, use range-based for loops (or iterators) if possible.

Summary

std::array is a great replacement for built-in fixed arrays. It's efficient, in that it doesn’t use any more memory than built-in fixed arrays. The only real downside of a std::array over a built-in fixed array is a slightly more awkward syntax, that you have to explicitly specify the array length (the compiler won’t calculate it for you from the initializer), and the signed/unsigned issues with size and indexing. But those are comparatively minor quibbles — we recommend using std::array over built-in fixed arrays for any non-trivial array use.

6.16 -- An introduction to std::vector
Index
6.14 -- Pointers to pointers and dynamic multidimensional arrays

186 comments to 6.15 — An introduction to std::array

  • p1

    So what is the reason to use this over normal arrays?

  • Hai Linh

    Why doesn't operator[] have bounds checking? After all, one can overload operator[] to add bounds checking, so why wouldn't std::array's operator[]?

    Edit: We learn exceptions in chapter 14, not chapter 15 (chapter 15 is about smart pointers)

  • Samira Ferdi

    Hi, Alex and Nascardriver!

    How to delete array's element?

  • potterman28wxcv

    Hello! Do you have any thoughts on using `auto` instead of `std::array<int, 5>::size_type` in the for loop?

    • The issue is that you need a 0 of type `std::array<int, 5>::size_type`, but you won't get that from the array.

      You might be tempted use `myArray.size() - 1` to initialize `i` and try to iterate in reverse.
      But unsigned integers get you, this will cause an infinite loop.

      The only way to get the start of the array is through `myArray.begin()`, but that gives you an iterator. Since the whole point of a regular for-loop is that you have an index, this doesn't help.

      `std::array` is guaranteed to use `std::size_t` as its index type (@Alex), so you can use that. This isn't guaranteed for all standard containers, check cppreference if you're unsure what your container's types are

      std::vector with an implementation defined index
      https://en.cppreference.com/w/cpp/container/vector#Member_types

      std::array with a guaranteed index
      https://en.cppreference.com/w/cpp/container/array#Member_types

  • Anastasia

    Hi!
    Do we have to write a separate function for every std::array's length we eventually may want to pass to that function? I mean isn't it a bigger downside than the fact that the regular array decays to a pointer if we can just pass it's length along with it and the same function will work for (build-in) arrays of all sizes?

    And a bit unrelated question, but in S.4.8 Alex wrote that `auto` keyword shouldn't work with function parameters. Yet, this snippet compiles just fine and without any warnings and works as expected. Has it been changed in C++17?

    • Hi!

      `std::array` always has to know its size. You could let the compiler generate functions for different sizes by using templates.
      If you want dynamically sized lists, `std::vector` is what you're looking for (Covered later in this chapter).

      `auto` cannot be used for function parameters. Make sure you followed lesson 0.10 and disabled compiler extensions.

      • Anastasia

        > `auto` cannot be used for function parameters. Make sure you followed lesson 0.10 and disabled compiler extensions.

        You're right, I've added `-pedantic-errors` flag and it doesn't compile now. I'd never have thought it's just an extension.

        I'll wait untill we learn about templates then. Thank you!

  • noobmaster

    Is that because arr1 decays to pointer when passed to .size() function?

  • Tamara

    I'm a bit confused by this part: "Due to a curious decision, the size() function and array index parameter to operator[] use a type called size_type, which is defined by the C++ standard as an unsigned integral type. Our loop counter/index (variable i) is a signed int."
    But if that's the case then haven't we've been having type mismatches in our code every time we would use for loops with built-in fixed arrays, for example like this:

    Now, by using typeid(size(myArray)).name(), I can see that the return type of size() is, indeed, an unsigned int and I do get a warning for it. But when I remove that, I get no type mismatch warning for the array index myArray[i]. Why is that? And also, how could I see the type of the array index parameter using typeid? If I just used typeid(myArray[i]).name() that would return the type of the array element, int in this case.

    Thanks in advance.

    • Line 4 should cause a warning, because you're comparing signed to unsigned. If it doesn't make sure you enabled sign comparison warnings in your compiler settings.
      Line 6 doesn't cause a warning, because array indexes only have to be integral types. This is not the case for `std::array`. `std::array` wants its `size_type` type as an index.

      • Tamara

        Oh, so it differs with std::array in that regard. But then one more thing. When I copied this entire piece of code from the lesson into VS:

        it gives me a warning for Line 9, for the "myArray.size()" as it should. But in the lesson it also says that the array index "myArray[i]" is a type mismatch and I'm not getting a warning back for that line. I'm assuming the sign comparison warnings are enabled since I'm getting a warning for Line 9.

        • Line 9 is a sign comparison,
          Line 10 is a sign conversion.
          Sorry, I don't know how to control warnings in VS, you'll have to look it up on your own.

          • Tamara

            Ah, I get it now. Thanks for the help!

          • Yaroslav

            can i ask why would we not just use

            or even better cast .size() to signed so 'i' will remain signed and we could use it down in comparison with signed by code

            ? thank you

            • > why would we not just use `unsigned int i`
              For one, we don't know that `arr.size()` returns an unsigned int. We could use `std::size_t`, but that's and unsigned integer type and looping over unsigned integers (Or anything involving unsigned integers, really) is prone to wrap arounds or unexpected signed -> unsigned promotions. It's easier to stay with signed integers as long as you don't need the high positive range of unsigned.
              A cast is the preferred solution. Since C++20, you can replace `arr.size()` with `std::ssize(arr)`, which returns the size as a signed integer, making the cast unnecessary.

  • darshan

    Why does

    give an error?

  • Why i am getting error in the above program?
    But in this one.

    • Snippet 1
      * Line 5: Initialize your variables with brace initializers.
      * Line 2: Limit your lines to 80 characters in length for better readability on small displays.

      Snippet 2
      * Line 5: Initialize your variables with brace initializers.

      If you're getting errors, post the error message.
      You're using @std::string without including <string>.

      • Oh! I actually never included string library unless i had to do string operations as it worked all the time and this is the first time i used std::string  in global scope. It worked when i included string library. But it also works when i initialise keywords array inside the main even without using string library.

        Why this happens ?
        Thanks a lot for the suggestions. :)

  • Shri

    Hi Alex, in this chapter you have mentioned that one of the reasons to use std::array is that, when passing std:array to a function (where the function parameter is a reference to the argument), it does not decay into a pointer.

    But in later chapters you show that even built-in arrays can be passed by reference (along with mentioning the length of array) such that the argument does not decay into a pointer.

    Based on this, do you think this advantage (std:array not decaying into a pointer when passed by reference into a function) should be removed from this chapter?

Leave a Comment

Put all code inside code tags: [code]your code here[/code]