175

If I want to build a very simple array like:

int myArray[3] = {1, 2, 3};

Should I use std::array instead?

std::array<int, 3> a = {{1, 2, 3}};

What are the advantages of using std::array over usual ones? Is it more performant? Just easier to handle for copy/access?

Jan Schultke
43.1k8 gold badges105 silver badges186 bronze badges
asked May 15, 2015 at 15:27
9
  • 5
    defining multi dimensional array with std:: will be difficult Commented May 15, 2015 at 15:33
  • 36
    @goGud: Not difficult, just more verbose. Commented May 15, 2015 at 15:34
  • 2
    Pointer decay, taking a reference etc..., many things are strange about c-arrays. The iterator may be a pointer in case of c-arrays and for (auto i = ++std::begin(myArray); . . . may not even compile (it seems that temporaries of fundamental type are not mutable, at least not with clang 6) Commented May 13, 2018 at 17:14
  • 1
    Initialization also differs magically: struct Direction { int32_t dw; int32_t dh; }; and static const Direction DIRECTIONS[DIRECTIONS_COUNT] { { -1, 1}, {0,1}, {1,1} , { 1, 0 }, {1,-1}, {0,-1} , {-1,-1}, {-1,0} }; compiles. But if you change to a std::array<Direction,DIRECTIONS_COUNT> with same initializer list, suddenly you get "too many initializers" error. (VS 2019 Community with language = C++17) Commented Oct 8, 2019 at 21:58
  • 3
    @Marc.2377 std::array is just a wrapper for C-style arrays. If I'm not mistaking, in some earlier versions of C++ you couldn't initialize it with single brackets. However, don't take my word for it, since I haven't used them much Commented Jul 20, 2020 at 9:57

8 Answers 8

186

What are the advantages of using std::array over usual ones?

It has friendly value semantics, so that it can be passed to or returned from functions by value. Its interface makes it more convenient to find the size, and use with STL-style iterator-based algorithms.

Is it more performant ?

It should be exactly the same. By definition, it's a simple aggregate containing an array as its only member.

Just easier to handle for copy/access ?

Yes.

answered May 15, 2015 at 15:32

1 Comment

any disadvantage? such as bigger code size?
67

A std::array is a very thin wrapper around a C-style array, basically defined as

template<typename T, size_t N>
struct array
{
 T _data[N];
 T& operator[](size_t);
 const T& operator[](size_t) const;
 // other member functions and typedefs
};

It is an aggregate, and it allows you to use it almost like a fundamental type (i.e. you can pass-by-value, assign etc, whereas a standard C array cannot be assigned or copied directly to another array). You should take a look at some standard implementation (jump to definition from your favourite IDE or directly open <array>), it is a piece of the C++ standard library that is quite easy to read and understand.

answered May 15, 2015 at 15:40

Comments

40

std::array is designed as zero-overhead wrapper for C arrays that gives it the "normal" value like semantics of the other C++ containers.

You should not notice any difference in runtime performance while you still get to enjoy the extra features.

Using std::array instead of int[] style arrays is a good idea if you have C++11 or boost at hand.

answered May 15, 2015 at 15:31

Comments

39

Is it more performant ?

It should be exactly the same. By definition, it's a simple aggregate containing an array as its only member.

The situation seems to be more complicated, as std::array does not always produce identical assembly code compared to C-array depending on the specific platform.

I tested this specific situation on godbolt:

#include <array>
void test(double* const C, const double* const A,
 const double* const B, const size_t size) {
 for (size_t i = 0; i < size; i++) {
 //double arr[2] = {0.e0};//
 std::array<double, 2> arr = {0.e0};//different to double arr[2] for some compiler
 for (size_t j = 0; j < size; j++) {
 arr[0] += A[i] * B[j];
 arr[1] += A[j] * B[i];
 }
 C[i] += arr[0];
 C[i] += arr[1];
 }
}

GCC and Clang produce identical assembly code for both the C-array version and the std::array version.

MSVC and ICPC, however, produce different assembly code for each array version. (I tested ICPC19 with -Ofast and -Os; MSVC -Ox and -Os)

I have no idea, why this is the case (I would indeed expect exactly identical behavior of std::array and c-array). Maybe there are different optimization strategies employed.

As a little extra: There seems to be a bug in ICPC with

#pragma simd 

for vectorization when using the c-array in some situations (the c-array code produces a wrong output; the std::array version works fine).

Unfortunately, I do not have a minimal working example for that yet, since I discovered that problem while optimizing a quite complicated piece of code.

I will file a bug-report to intel when I am sure that I did not just misunderstood something about C-array/std::array and #pragma simd.

answered Oct 14, 2018 at 18:41

2 Comments

can be considered as a compiler bug?
The one guy who actually tested it, near the bottom of upvoted answers.
22

std::array solves numerous issues which C-style arrays have. It's not about performance for the most part; it's about correctness and convenience. Here is the list of issues with C-style arrays that std::array solves.

1. Arrays cannot be returned from functions

This one is particularly annoying. Especially for tasks like initializing look-up tables, returning arrays is useful:

constexpr auto lookup = [] {
 std::array<int, 128> result;
 // compute the contents
 return result;
}();

2. The hypothetical syntax for returning arrays is cursed

int get_array()[10];
auto get_array() -> int[10]; // workaround: trailing return types

According to the C declaration syntax, this is how a function get_array which returns an array of 10 ints would be declared. This syntax is very surprising, and one of the reasons why it's very unlikely to ever be standardized in C.

3. Arrays cannot be assigned

int arr[] = {0, 1, 2, 3};
arr = something_else; // ill-formed

Assigning arrays is fairly common for small arrays, such as pairs or triples. This feels particularly inconsistent because the syntax = can be used for initialization.

4. Arrays cannot be initialized to other arrays

int data[] = {0, 1};
int copy[] = data;

This is yet another restriction which feels arbitrary. What makes it even worse is that copy-initialization is valid for arrays in principle, just not using another array instead of a braced-init-list.

5. Comparison of arrays with == is a common mistake

int arr[] = {0, 1, 2, 3};
arr == arr; // true, but doesn't compare contents, but is pointer comparison

This is a common mistake, which is why array comparison was deprecated in C++23 and is likely going to be removed in C++26.

See also Why does == equality comparison between arrays not work?

6. Arrays decay to pointers, giving them surprising semantics

char arr[] = "hello ";
if (arr); // always true, even if array contains an empty string
+arr; // OK, but what does it mean to apply unary plus to an array?!
arr + 1; // OK, but result is not "hello 1", it is "ello "

The list goes on and on. The fact that arrays decay to pointers often leads to counter-intuitive behavior. Most of this behavior is not yet deprecated.

7. Arrays might be variable length arrays (VLAs)

int size = rand();
int vla[size]; // could be OK if the compiler supports VLAs as an extension
std::array<int, size> arr; // error, as expected

Without compiler warnings (-Wvla for GCC/clang), we can inadvertently create a VLA. This makes code non-portable because not every compiler has support for VLAs.

8. Array type adjustment in function parameters is misleading

void foo(int arr[4]) { // equivalent to accepting a parameter of type int*
 sizeof(arr) / sizeof(arr[0]); // = sizeof(void*) / sizeof(int), most likely 2
}
void foo(std::array<int, 4> arr) {
 arr.size(); // 4, correct
}

Parameters of array type in function parameters are adjusted to pointer types. This means that sizeof(arr) doesn't work properly, and the provided size [4] is actually meaningless.

This is very surprising, and using sizeof in conjunction with pointers when arrays were expected is one of the most common beginner mistakes in C.

9. Array sizes can't be deduced from function parameters

template <std::size_t N>
void foo(int arr[N]); // N can't be deduced from the array parameter
template <std::size_t N>
void foo(int (&arr)[N]); // workaround with complicated syntax

This issue is a consequence of the type adjustment to pointers in the prior point. There is no array type in the parameter from which N could be deduced. A workaround is necessary, but this workaround is not pretty.

10. Arrays lead to special cases in generic code

Due to all the aforementioned irregularities, arrays require special cases in generic code. To name some examples:

  • std::swap must have an overload for arrays because arrays are immovable
  • arrays aren't classes, so lots of free functions like std::size, std::begin, std::empty, etc. are necessary that have a special case for arrays

Any developer who writes a library which contains range.begin() instead of std::begin(range) has to fear their code breaking if a user uses C-style arrays. Of course, using std::array doesn't solve the underlying issue, but it means that you never suffer from a library developer having made the false assumption that .begin() is always valid.

Furthermore, these countless special cases still don't cover everything. You can use std::swap to do an element-wise swap of two arrays, but you cannot use std::exchange to exchange arrays element-wise (because arrays cannot be returned from functions).

11. Arrays may have poor performance due to aliasing

It is easy to inadvertently write code which has performance penalties caused by aliasing. Consider the following functions which are meant to serialize a 32-bit unsigned integer array into a byte array with little endian byte order.

std::array<std::byte, 4> serialize_le(unsigned x) {
 return {
 std::byte(x >> 0),
 std::byte(x >> 8),
 std::byte(x >> 16),
 std::byte(x >> 24)
 };
}
void write_le_n(std::byte* mem, std::array<unsigned, 1024>& numbers) {
 for (unsigned n : numbers) {
 auto bytes = serialize_le(n);
 std::memcpy(mem, &bytes[0], sizeof(bytes));
 mem += 4;
 }
}
void write_le(std::byte mem[], unsigned x) {
 mem[0] = std::byte(x >> 0);
 mem[1] = std::byte(x >> 8);
 mem[2] = std::byte(x >> 16);
 mem[3] = std::byte(x >> 24);
}
void write_le_n(std::byte* mem, unsigned x[1024]) {
 for (unsigned i = 0; i < 1024; ++i) {
 write_le(mem + i * 4, x[i]);
 }
}

See live example at Compiler Explorer

Intuitively, it would seem like both code samples are doing pretty much the same thing. However, the code generation for the second sample is much worse. There is absolutely no auto-vectorization; it's an extremely naive loop.

std::array can often indicate to the compiler that no aliasing takes place, or if there is overlap between memory regions, it cannot be partial. This is a boost to optimizations.

Conclusion

std::array solves countless problems with C-style arrays. For the most part, this isn't about performance, although std::array may be better in specific cases. It's about C-style arrays having confusing syntax, common pitfalls, arbitrary restrictions, and other issues.

See Also

answered Sep 14, 2023 at 14:55

Comments

13

std::array has value semantics while raw arrays do not. This means you can copy std::array and treat it like a primitive value. You can receive them by value or reference as function arguments and you can return them by value.

If you never copy a std::array, then there is no performance difference than a raw array. If you do need to make copies then std::array will do the right thing and should still give equal performance.

answered May 15, 2015 at 15:33

Comments

2

You will get the same perfomance results using std::array and c array If you run these code:

std::array<QPair<int, int>, 9> *m_array=new std::array<QPair<int, int>, 9>();
 QPair<int, int> *carr=new QPair<int, int>[10];
 QElapsedTimer timer;
 timer.start();
 for (int j=0; j<1000000000; j++)
 {
 for (int i=0; i<9; i++)
 {
 m_array->operator[](i).first=i+j;
 m_array->operator[](i).second=j-i;
 }
 }
 qDebug() << "std::array<QPair<int, int>" << timer.elapsed() << "milliseconds";
 timer.start();
 for (int j=0; j<1000000000; j++)
 {
 for (int i=0; i<9; i++)
 {
 carr[i].first=i+j;
 carr[i].second=j-i;
 }
 }
 qDebug() << "QPair<int, int> took" << timer.elapsed() << "milliseconds";
 return 0;

You will get these results:

std::array<QPair<int, int> 5670 milliseconds
QPair<int, int> took 5638 milliseconds

Mike Seymour is right, if you can use std::array you should use it.

answered Oct 1, 2021 at 5:47

Comments

1

While std::array has some advantages, as outlined in the other helpful answers, some have stated things like, "... there is no performance difference than a raw array."

This simply is not true and is misleading to anyone working in true embedded development (time critical, bare metal).

Quick and dirty test using an STM32G0B1 running at 50MHz (CPU): Writing to a std::array takes approximately 5 μS longer than to a raw C style array. To me, this is a significant performance difference that cannot be ignored.

answered Apr 28, 2023 at 9:50

2 Comments

care to post your benchmark?
@MattSpicer It was just a quick test. Easy enough to do and not worth keeping. A simple set/clear of a test pin to give a pulse to scope and measure time it takes to write to one type of array vs another.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.