As an alternative, even if the element type is not default-constructible, you can still initialize a buffer of uninitialized memory, and create a std::array
from that using std::to_array
and a pointer cast.
Be warned: The elements of the temporary array are not destructed after being moved from! Moving leaves most types in a trivially-destructible state, but this might not be true in every case.
Sample code:
#include <array>
#include <memory> // uninitialized_move
#include <ranges>
#include <utility>
using std::size_t;
struct Int{
int v;
constexpr Int(int v) noexcept : v{v} {}
};
constexpr size_t array_len = 24;
std::array<Int, array_len> generate_array() noexcept {
// Generator for the elements of the returned array:
constexpr auto source = std::ranges::transform_view(
std::ranges::iota_view(0, static_cast<int>(array_len)),
[](const int i)constexpr{return Int(11*(i+1));}
);
// Buffer of uninitialized memory:
alignas(Int) std::array<char, sizeof(Int)*array_len> scratch;
std::uninitialized_move_n(
source.begin(),
array_len,
reinterpret_cast<Int*>(scratch.data())
);
return std::to_array<Int, array_len>(std::move(*reinterpret_cast<Int(*)[array_len]>(scratch.data())));
}
#include <cstdio>
#include <iostream>
int main() {
static const auto xs = generate_array();
for (const Int& x : xs) {
std::cout << x.v << ' ';
}
std::cout << '\n';
return EXIT_SUCCESS;
}
The current version of MSVC on the Compiler Explorer lacks std::uninitialized_move_n
, but Microsoft does support it. As a workaround, you might fall back on std::uninitialized_move
.
You could also use the generator above to construct a std::vector
and copy your std::array
from that. For large arrays, which might cause an (ahem) stack overflow, this has the advantage that it constructs the temporary copy on the heap.
#include <array>
#include <cassert>
#include <ranges>
#include <utility>
#include <vector>
using std::size_t;
struct Int{
int v;
constexpr Int(int v) noexcept : v{v} {}
};
constexpr size_t array_len = 24;
std::array<Int, array_len> generate_array() { // Can now throw bad_alloc.
// Generator for the elements of the returned array:
constexpr auto source = std::ranges::transform_view(
std::ranges::iota_view(0, static_cast<int>(array_len)),
[](const int i)constexpr{return Int(11*(i+1));}
);
std::vector<Int> scratch(source.begin(), source.end());
assert(scratch.size() == array_len);
return std::to_array<Int, array_len>(std::move(*reinterpret_cast<Int(*)[array_len]>(scratch.data())));
}
#include <cstdio>
#include <iostream>
int main() {
static const auto xs = generate_array();
for (const Int& x : xs) {
std::cout << x.v << ' ';
}
std::cout << '\n';
return EXIT_SUCCESS;
}
Try it on the Godbolt compiler explorer.
Update
As of 2024, a more-useful, but potentially non-portable, solution for large arrays is to bypass std::to_array
and directly reinterpret_cast
the buffer as a pointer to a std::array
. This causes the return object to be move-constructed, which for a large array of this type calls memcpy
. For whatever reason, if you use std::to_array
, some compilers will spend an excessive amount of time and memory doing template metaprogramming on a large array.
Although the Standard effectively guarantees that a built-in array T[N]
, a range of pointers from the start to the end of the array, and the range [ data(), data()+size() )
of a std::array
or std::vector
have the same value representation, the first element of an array is not formally layout-convertible to an array. There is also no requirement that a std::array
have the same address as its data()
, or that it not have a stricter required alignment. So, this works on all the most-used compilers, but try it at your own risk. I do, however, put in a static_assert
that the address of a std::array<T, 1>
is the same as its data, as a sanity check against the most-likely way this could break.
Therefore, this version works on the compiler explorer at sizes where the more-portable ones break down.
#include <array>
#include <cassert>
#include <ranges>
#include <utility>
#include <vector>
using std::size_t;
struct Int{
int v;
constexpr Int(int v) noexcept : v{v} {}
};
// C++20 does not guarantee that a std::array is layout-compatible with a built-in array, so:
static constexpr std::array<Int,1> layout_check = {Int(0)};
static_assert(static_cast<const void*>(std::addressof(layout_check)) == static_cast<const void*>(layout_check.data()), "");
constexpr size_t array_len = 500'000;
std::array<Int, array_len> generate_array() noexcept {
// Generator for the elements of the returned array:
constexpr auto source = std::ranges::transform_view(
std::ranges::iota_view(0, static_cast<int>(array_len)),
[](const int i)constexpr{return Int(11*(i+1));}
);
// Buffer of uninitialized memory:
alignas(Int) std::array<char, sizeof(Int)*array_len> scratch;
std::uninitialized_move_n(
source.begin(),
array_len,
reinterpret_cast<Int*>(scratch.data())
);
return std::move(*reinterpret_cast<std::array<Int, array_len>*>(scratch.data()));
}
#include <cstdio>
#include <iostream>
int main() {
static const auto xs = generate_array();
for (size_t i = 0; i < xs.size(); i += 1000) {
std::cout << xs[i].v << ' ';
}
std::cout << '\n';
return EXIT_SUCCESS;
}
Although you likely want to create large arrays on the heap:
#include <array>
#include <cassert>
#include <ranges>
#include <utility>
#include <vector>
using std::size_t;
struct Int{
int v;
constexpr Int(int v) noexcept : v{v} {}
};
constexpr size_t array_len = 500'000;
// C++20 does not guarantee that a std::array is layout-compatible with a built-in array, so:
static constexpr std::array<Int,1> layout_check = {Int(0)};
static_assert(static_cast<const void*>(std::addressof(layout_check)) == static_cast<const void*>(layout_check.data()), "");
std::array<Int, array_len> generate_array() { // Can throw bad_alloc.
// Generator for the elements of the returned array:
constexpr auto source = std::ranges::transform_view(
std::ranges::iota_view(0, static_cast<int>(array_len)),
[](const int i)constexpr{return Int(11*(i+1));}
);
std::vector<Int> scratch(source.begin(), source.end());
assert(scratch.size() == array_len);
return std::move(*reinterpret_cast<std::array<Int, array_len>*>(scratch.data()));
}
#include <cstdio>
#include <iostream>
int main() {
static const auto xs = generate_array();
for (size_t i = 0; i < xs.size(); i += 1000) {
std::cout << xs[i].v << ' ';
}
std::cout << '\n';
return EXIT_SUCCESS;
}