c++stdarray

std::array - How can I create a fixed size class member of an object that can't get default constructed


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.


Solution

  • 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)... } }
        {}
    };