2

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!

5
  • 3
    "Im trying to understand how std::array constructor work" It doesn't have a constructor; that's how it works. Are you asking what that code is doing? Commented Dec 20, 2021 at 19:16
  • 3
    What you are looking at is a deduction guide, which is used for CTAD, not a constructor. Commented Dec 20, 2021 at 19:16
  • 2
    std::array is an aggregate type, that means it doesn't have a user-provided constructor Commented Dec 20, 2021 at 19:18
  • oh thats confusing, it doesnt have a constructor and how it initialize an given array to its array like this ex: std::array<int, 5> nums = {1, 2, 3}; Commented Dec 20, 2021 at 19:19
  • 2
    It does so by using the rules of aggregate initialization: en.cppreference.com/w/cpp/language/aggregate_initialization Commented Dec 20, 2021 at 19:20

2 Answers 2

2

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    
}
Sign up to request clarification or add additional context in comments.

4 Comments

oh ok i get it now, im still confused how does the MyArray arr have the values of {1, 2, 3} and how would the constructor looks like (if i was defined).
@ZakiMkn added into the answer explanation regarding aggregate initialization. As for how the constructor would look like, well the all idea is that it doesn't need a constructor so this is more of an hypothetical question.
oh okay, btw why i cannot do aggregate initialization when the array is a private data member?
@ZakiMkn if the class (or struct) has private or protected data members it is not an aggregate type and thus cannot use aggregate initialization.
2

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

2 Comments

Your answer made stuff much more clear, thank u. But i kind of have a lack of knowledge about c++ compiler things, does every c++ program have to get converted into assembly lang? and is the machine code created from the assembly or c++ code?
@ZakiMkn Usually, the compilers convert CPP code to object files that contain machine code. Assembly is not required in that case. Assembly languages are for humans. Computers can't read assembly code, but only binary machine code.

Your Answer

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.