Initializing an std::array
by variadic template arguments, starting from a given index can be done the following way:
#include <array>
template <typename T, size_t N>
struct A
{
template <typename ... Ts>
A(size_t i, Ts ... vals)
{
constexpr size_t P = sizeof...(vals);
std::array<T, P> temp{ vals... };
for (size_t j = 0; j < P; ++j)
{
arr[i + j] = temp[j];
}
}
std::array<T, N> arr;
};
But is it possible to achieve the same without converting the parameter pack to a temporary tuple or another std::array?
You may use std::index_sequence
and delegating constructor:
template <typename T, size_t N>
struct A
{
template <typename ... Ts>
A(size_t i, Ts&& ... vals)
:
A(std::index_sequence_for<Ts...>{}, i, std::forward<Ts>(vals)...)
{}
template <std::size_t...Is, typename ... Ts>
A(std::index_sequence<Is...>, size_t i, Ts&& ... vals)
{
int dummy[] = {0, ((arr[i + Is] = vals), void(), 0)...};
static_cast<void>(dummy); // Avoid warning for unused variable
}
std::array<T, N> arr;
};
Or with Folding expression of C++17,
template <std::size_t...Is, typename ... Ts>
A(std::index_sequence<Is...>, size_t i, Ts&& ... vals)
{
(static_cast<void>(arr[i + Is] = vals), ...);
}
Although it involves C++17 fold expressions, the simplest possible solution inspired by the comment of Jarod42 is:
#include <array>
template <typename T, size_t N>
struct A
{
template <typename ... Ts>
A(Ts ... vals)
{
size_t i = 1; // starting index, can be an argument
((arr[i++] = vals) , ...);
}
std::array<T, N> arr;
};
int main()
{
A<int, 4> a(1, 2, 3);
return 0;
}
Compiling this with Clang 3.6 or above with optimization level -O1
and -std=c++1z
flag the generated assembly is:
A<int, 4ul>::A<int, int, int>(int, int, int): # @A<int, 4ul>::A<int, int, int>(int, int, int)
pushq %rbp
pushq %r15
pushq %r14
pushq %rbx
pushq %rax
movl %ecx, %r14d
movl %edx, %r15d
movl %esi, %ebx
movq %rdi, %rbp
movl $1, %esi
callq std::array<int, 4ul>::operator[](unsigned long)
movl %ebx, (%rax)
movl $2, %esi
movq %rbp, %rdi
callq std::array<int, 4ul>::operator[](unsigned long)
movl %r15d, (%rax)
movl $3, %esi
movq %rbp, %rdi
callq std::array<int, 4ul>::operator[](unsigned long)
movl %r14d, (%rax)
addq $8, %rsp
popq %rbx
popq %r14
popq %r15
popq %rbp
retq
Which is equivalent to the test case
template <typename T, size_t N>
struct B
{
B(T x, T y, T z)
{
arr[1] = x;
arr[2] = y;
arr[3] = z;
}
std::array<T, N> arr;
};
int main()
{
B<int, 4> b(1, 2, 3);
return 0;
}
with generated assembly
B<int, 4ul>::B(int, int, int): # @B<int, 4ul>::B(int, int, int)
pushq %rbp
pushq %r15
pushq %r14
pushq %rbx
pushq %rax
movl %ecx, %r14d
movl %edx, %r15d
movl %esi, %ebx
movq %rdi, %rbp
movl $1, %esi
callq std::array<int, 4ul>::operator[](unsigned long)
movl %ebx, (%rax)
movl $2, %esi
movq %rbp, %rdi
callq std::array<int, 4ul>::operator[](unsigned long)
movl %r15d, (%rax)
movl $3, %esi
movq %rbp, %rdi
callq std::array<int, 4ul>::operator[](unsigned long)
movl %r14d, (%rax)
addq $8, %rsp
popq %rbx
popq %r14
popq %r15
popq %rbp
retq
It's worth to note that the generated assembly of the two case is equivalent.
template<std::size_t I>
using index_t = std::integral_constant<std::size_t, I>;
template<class T, std::size_t=0, class...Args>
T make(Args&&...args){ return T(std::forward<Args>(args)...); }
template <class T, size_t N>
struct A {
template <std::size_t I, class...Ts>
A(index_t<I>, Ts&&... vals):
A(std::make_index_sequence<I>{}, std::forward<Ts>(vals)...)
{}
template <std::size_t...Is, class...Ts>
A(std::index_sequence<Is...>, Ts&&...vals):
arr{{ make<T,Is>()..., std::forward<Ts>(vals)... }}
{}
// ...
};
this requires a compile-time known offset, but avoids creating a default-constructed array then assigning to it. It however also involves creating a pile of T
s and moving them into the array. Ick!
Backing up a second, we know that the array is a standard layout block of T
s. Let's cheat.
// stores the raw buffer for the array, and constructs it with
// some care:
template<class T>
using storage = std::aligned_storage_t<sizeof(T),alignof(T)>;
template<class T, size_t N>
struct A_storage {
storage<std::array<T, N>> raw_arr;
std::array<T,N>& arr() {
return *reinterpret_cast<std::array<T, N>*>(&raw_arr);
}
std::array<T,N> const& arr() const {
return *reinterpret_cast<std::array<T, N> const*>(&raw_arr);
}
template<class...Ts>
static void make_arr(storage<std::array<T, N>>& retval, std::size_t offset, Ts&&...ts) {
auto* ptr = reinterpret_cast<T*>(&retval);
try {
std::size_t ctor_count = 0;
for (size_t i = 0; i < offset; ++i) {
++ctor_count;
::new((void*)(ptr+i)) T();
}
::new((void*)(ptr +offset)) std::array<T, sizeof...(Ts)>{{
(++ctor_count, void(), std::forward<Ts>(ts))...
}};
for (size_t i = offset+sizeof...(Ts); i < N; ++i) {
++ctor_count;
::new((void*)(ptr+i)) T();
}
} catch(...) {
// ctor_count is the count of constructors *attempted*
// so ptr[ctor_count-2] is the last one we *succeeded at*
// destroy everything we successfully constructed.
// ctor_count has to be at least 1, as we cannot throw before
// incrementing. Let us peel it off.
--ctor_count;
// iterate backwards from ctor_count-1 down to 0, so we destroy
// in reverse order of constructing:
for (size_t i = 1; i <= ctor_count; ++i) {
ptr[ctor_count-i].~T();
}
throw;
// I use attempted, because the initializer list syntax of array
// construction doesn't let me increment after I provide the value
// for the place I'm constructing. I can, however, increment before.
}
}
template<class...Ts>
A_storage(std::size_t offset, Ts&&...ts)
{
static_assert(sizeof...(Ts)<=N, "too much data!");
ASSERT(offset+sizeof...(Ts)<=N);
make_arr(raw_arr, offset, std::forward<Ts>(ts)...);
}
// only runs if the ctor above completes, which means
// everything was constructed:
~A_storage() {
for (size_t i = 1; i <= N; ++i) {
arr()[N-i].~T();
}
}
};
template<std::size_t N, class T>
struct A:A_storage {
template<class...Ts>
A(std::size_t offset, Ts&&...ts):
A_storage(offset, std::forward<Ts>(ts)...)
{}
};
Everything is constructed in-place. No need for T(T&&)
support! (unless that is how you pass in the parameters to the array, but that is your problem not mine)
I tried to make destruction work in an exception-heavy environment. I may have gotten it wrong.
The only temporary variable (the return value of make_arr
) should be elided by NRVO. On the other hand, a compiler might be a slacker and not elide into class member constructors. Bad compiler.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With