I'm building a container class for any type T
that needs to keep a list of T
that has length known at compile time. I'm struggling to find a data structure that can work well for this problem, as std::array
complains when I try to initialize my object in the body of my constructor. Critically, the type T
is not default constructible.
Here is a snippet that about corresponds to the functionality that I am looking for
#include <array>
#include <cstddef>
#include <type_traits>
struct not_default_constructible
{
not_default_constructible(int){}
};
static_assert(!std::is_default_constructible_v<not_default_constructible>);
/*
Does not compile!
*/
template<typename T, long N>
struct array_of_ndc
{
std::array<T,N> arr;
array_of_ndc(T init)
{
for(size_t i = 0; i < N; i++)
{
arr[i] = init;
}
}
};
int main()
{
array_of_ndc<not_default_constructible, 2> ndc(not_default_constructible{2});
}
The trouble with this snippet (which does not compile!) is that the compiler wants me to initialize my std::array
before the body of my constructor.
Here is one solution I wrote, but it very much feels like I'm fighting with the compiler with this (and it breaks everything I love about std::array
, i.e. iterators), and I wanted to see if there is a more STL friendly solution to this.
template<typename T, long N>
struct no_construction_arr
{
alignas(T) char data[N * sizeof(T)];
template<typename U>
requires(std::constructible_from<T,U>)
no_construction_arr(U&& init)
{
for(size_t i = 0; i < N; i++)
{
new (&data[i * sizeof(T)]) T (init);
}
}
T* operator[](size_t index)
{
return reinterpret_cast<T*>(&data[index * sizeof(T)]);
}
~no_construction_arr()
{
for(size_t i = 0; i < N; i++)
{
(reinterpret_cast<T*>(&data[i * sizeof(T)]))->~T();
}
}
};
I'll also add that, I know I can do this with std::vector
. This is about trying to take advantage of the fact that I know the size of the list that I will need ahead of time, and so it seems wasteful to make an unneeded dynamic allocation.
To get a sequence or repetition where you don't already have a parameter pack to expand, use std::make_index_sequence
:
#include <utility>
#include <array>
struct not_default_constructible
{
not_default_constructible(int){}
};
template<typename T, unsigned long N>
struct array_of_ndc
{
public:
std::array<T,N> arr;
explicit array_of_ndc(const T& init)
: array_of_ndc(init, std::make_index_sequence<N>{})
{}
private:
template <std::size_t ...I>
explicit array_of_ndc(const T& init, std::index_sequence<I...>)
: arr{ { (static_cast<void>(I), init)... } }
{}
};