Home > Mobile >  How does std::array constructor initialize its array?
How does std::array constructor initialize its array?

Time:12-21

Im trying to understand how std::array constructor work and how can he take a array and initialize it to its array.

I was searching on the standard library file and i find this piece of code

#if _HAS_CXX17
template <class _First, class... _Rest>
struct _Enforce_same {
    static_assert(conjunction_v<is_same<_First, _Rest>...>,
        "N4687 26.3.7.2 [array.cons]/2: "
        "Requires: (is_same_v<T, U> && ...) is true. Otherwise the program is ill-formed.");
    using type = _First;
};

template <class _First, class... _Rest>
array(_First, _Rest...) -> array<typename _Enforce_same<_First, _Rest...>::type, 1   sizeof...(_Rest)>;
#endif // _HAS_CXX17

Is this the constructor? how does it work exacly?

Thanks!

CodePudding user response:

As already mentioned in comments above, std::array is an aggregate type, so you are not calling a constructor but actually initializing a data member.

The code you point at in the question allows creation of std::array without stating array's type and size. This is done, with deduction guides, as seen in the code below.

Here is how it may look if you implement it by yourself:

template<typename T, std::size_t SIZE>
struct MyArray {
    T arr[SIZE];    
};

// MyArray deduction guides:
// similar code was added to std::array in C  17 to allow the
// creation of a2 below, without providing template arguments
template <class _First, class... _Rest>
MyArray(_First, _Rest...) -> MyArray<_First, 1   sizeof...(_Rest)>;

int main() {
    MyArray<int, 5> a1 = {1, 2, 3}; // since C  11
    MyArray a2 {1, 2, 3}; // deduced to MyArray<int, 3>, since C  17
    // creation of a2 is based on the deduction guides above
}

The code above ignores the case of sending different types to MyArray, like this:

MyArray a2 {1, 2.0, 3}; // still deduced to MyArray<int, 3> with our code

There are several options to treat the above: decide that the type of the array is based on the type of the first value (as we actually do in our naive implementation), decide that the type would be based on the common_type of the values, or disallow such initialization. The C standard decided to disallow it, so there is a need to check that all types are the same and raise compilation error if not, e.g. using static_assert. As done in the original code posted, using the _Enforce_same struct.


The initialization of the inner data member is based on aggregate initialization, values sent to a struct are passed into its fields like in the examples below:

struct A {
    int a;
};

struct B {
    int a, b;
};

struct C {
    int a[5];
};

int main {
    A a = {1}; // goes to a.a
    B b = {1, 2}; // goes to b.a and b.b
    C c = {1, 2, 2}; // goes into the array c.a    
}

CodePudding user response:

The answer by @Amir Kirsh is well detailed. So I don't want to repeat it. Instead, I'm inviting you to have a look at the assembly output of the following code:

#include <iostream>
#include <array>

int main( )
{
    // std::array<int, 5> arr = { 1, 2, 3 }; // std::array
    int arr[5] { 1, 2 , 3 }; // raw array (aka C-style array)

    for ( const auto& i : arr )
    {
        std::cout << i << '\n';
    }

    return 0;
}

You can check and compare both arrays' assembly codes here.
Just uncomment the std::array version and comment out the raw array version to see the assembly code for the std::array version.

But to make your job even easier, here is the code for both arrays:
std::array

        mov     rax, QWORD PTR .LC0[rip]
        mov     QWORD PTR [rsp 28], 0
        lea     rbx, [rsp 16]
        lea     rbp, [rsp 36]
        mov     QWORD PTR [rsp 16], rax
        mov     DWORD PTR [rsp 24], 3

raw array

        mov     rax, QWORD PTR .LC0[rip]
        mov     QWORD PTR [rsp 28], 0
        lea     rbx, [rsp 16]
        lea     rbp, [rsp 36]
        mov     QWORD PTR [rsp 16], rax
        mov     DWORD PTR [rsp 24], 3

YES! As can be seen, they are identical. The std::array is converted to its equivalent C-style array during the compile-time. It's just syntax-sugar. There is literally no need for a constructor or a destructor. Both are the same thing and are stored on the stack. They are trivially constructible which means they can be constructed just by incrementing the stack pointer register and a few other fast instructions.

Just like this:

#include <iostream>
#include <type_traits>
#include <array>

int main( )
{
    std::cout << std::boolalpha << "Is std::array<int, 5> trivially constructible: "
              << std::is_trivially_constructible< std::array<int, 5> >::value << '\n';

    std::cout << std::boolalpha << "Is int[5] trivially constructible: "
              << std::is_trivially_constructible< int[5] >::value << '\n';

The result:

Is std::array<int, 5> trivially constructible: true
Is int[5] trivially constructible: true
  • Related