0
votes

I am trying to build a std::array from the data of a std::vector. I have currently done this :

#include <vector>
#include <array>

int main(void)
{
    std::vector<int>    vec{{1, 2, 3, 4, 5}};
    std::array<int, 5>  arr{static_cast<int [5]>(vec.data())};

    (void)arr;
    return 0;
}

but gcc does not accept this cast :

error: invalid static_cast from type ‘int*’ to type ‘int [5]’

I thought an array could be used as a pointer so why cannot we do this cast ?

Edit: This is not a duplicate of Copy std::vector into std::array since my question is about initialization (construction) of an std::array; not copy into it.

Edit: I have done this :

#include <vector>
#include <array>

int main(void)
{
    std::vector<int>    vec{{1, 2, 3, 4, 5}};
    int                 *a(vec.data());
    std::array<int, 5>  arr{*reinterpret_cast<int (*)[5]>(&a)};

    (void)arr;
    return 0;
}

But array does not initialize... gcc says :

error: array must be initialized with a brace-enclosed initializer

4
What do you picture that will happen with this initialization? You provide the pointer and the size and the constructor copies it? And how do you think you can cast an int* to a int[5]?Chiel
@Chiel I want the array being initialized like with a c litteral array ; except I supply it from the data() method of the vector. If it is not possible, what is the reason ?Boiethios
@HostileFork Is it a way in C++ to do an array-to-pointer ? Why could not be possible here, since we know the address and the size of the array ?Boiethios

4 Answers

0
votes

std::array is an aggregate type; it does not have any user-defined constructors. Its single data member is an array, which cannot be passed in a function or constructor call; an array passed by value decays to a pointer to its first element, and an array passed by reference cannot be used to initialize an array data member.

This means that an array<T, N> can be initialized only by another array<T, N> or by a literal sequence of up to N values of type T in a {}-enclosed list.

From C++17, you will be able to use the library function to_array to convert a reference to array to a std::array:

std::array<int, 5>  arr{std::experimental::to_array(*reinterpret_cast<int (*)[5]>(&a))};

As you can see in the Possible implementation section, this is internally initializing each element separately:

return { {a[I]...} };
2
votes

"I thought an array could be used as a pointer so why cannot we do this cast?"

You can use arrays as pointers because they "decay" to them. See "What is array decaying?"

But the reverse is not true. (There's type checking on differently-sized arrays as well.)

While static_cast can be used to reverse some implicit conversions, this just isn't one of them. It's explicitly excluded from the list:

5) If a standard conversion sequence from new_type to the type of expression exists, that does not include lvalue-to-rvalue, array-to-pointer, function-to-pointer, null pointer, null member pointer, function pointer, (since C++17) or boolean conversion, then static_cast can perform the inverse of that implicit conversion.

So this might make you wonder how to convert a pointer into an array type. You can't, as "Arrays are second-class citizens in C (and thus in C++)." reinterpret_cast won't work either.

(If you try and use a C-style cast like (int [5])(vec.data()) you would likely get the more definitive message ISO C++ forbids casting to an array type 'int [5]'.)

So that's just how the language implementation falls out. And here what you're doing is trying to initialize a std::array whose raison d'etre is to really just wrap a C array:

This container is an aggregate type with the same semantics as a struct holding a C-style array T[N] as its only non-static data member.

So when it comes to initialization forms, it isn't going to be able to do anything you couldn't do for a C array. And if you have an integer pointer in your hand and want to initialize an int arr[5] = ... from that pointer, you are similarly out of luck. You have to copy it.

And in this case that could be done with...

Copy std::vector into std::array

2
votes

You can use recursive templates to achieve something similar to what you want. The trick is to construct the parameter list to the array constructor using the ... operator in a variadic template that has the required vector element indices as its variadic parameters. In the below implementation which achieves this, the construction of the array itself is then done in the template, and you can rely on return value optimisation to ensure no copying is done to get the values into the final array. This achieves your stated aim of initialising the array from a vector, although it is somewhat contrived and not necessarily something you would actually want to do.

#include <vector>
#include <array>
#include <iostream>

template<typename T, unsigned total_elem_count, unsigned remain_elem_count, unsigned... elements>
struct init_array_from_vec_helper
{   
    static std::array<T, total_elem_count> array_from_vec(const std::vector<T>& cont)
    {   
        return init_array_from_vec_helper<T, total_elem_count, remain_elem_count-1, remain_elem_count-1, elements...>::array_from_vec(cont);
    }   
};  


template<typename T, unsigned total_elem_count, unsigned... elements>
struct init_array_from_vec_helper<T, total_elem_count, 0, elements...>
{   
    static std::array<T, total_elem_count> array_from_vec(const std::vector<T>& cont)
    {   
        return std::array<T, total_elem_count>{cont[elements]...};
    }   
};  

template<typename T, unsigned total_elem_count>
std::array<T, total_elem_count> init_array_from_vec(const std::vector<T>& vec)
{   
    return init_array_from_vec_helper<int, total_elem_count, total_elem_count-1, total_elem_count-1>::array_from_vec(vec);
}   

int main(void)
{   
    std::vector<int>    vec{{1, 2, 3, 4, 5}};

    std::array<int, 5> arr(init_array_from_vec<int, 5>(vec));

    (void)arr;

    for(int i : arr)
        std::cout << i << std::endl;

    return 0;
}   
0
votes

From the cpp array reference :

The array classes are aggregate types, and thus have no custom constructors.

You should construct it with the default initialization, and then copy your vector content to it.