7

I am wondering if it's possible to initialize a std::array of objects with an implicitly deleted default constructor, without knowing a priori the size of the array because it's a template argument and so having lost the possibility of using an initializer list. Code follows, it breaks with a "call to implicitly-deleted default constructor of std::array<A, 3UL>"

struct A {
  A (int b, int c) : mb(b), mc(c) { }
  int mb;
  int mc;
};

template <size_t NR_A>
struct B {
  B (int b, int c) : 
    // <- how to initialize mAs here?
  { }
  std::array<A, NR_A> mAs;
};

B<3> inst(1,1);

edit: I'd like to initialize all the A's of mAs to A{1,1}

JFMR
  • 23,265
  • 4
  • 52
  • 76
Alex Darsonik
  • 597
  • 5
  • 18

3 Answers3

8

You may use delegating constructors and pack expansion

struct A {
    A(int b, int c) : b(b), c(c) { }
    A(const A&) = delete;
    A(A&&) = delete;
    int b;
    int c;
};

template <size_t N>
struct B {
  B (int b, int c) : B(b, c, std::make_index_sequence<N>{}) {}

  template<size_t... Is>
  B (int b, int c, std::index_sequence<Is...>) :
    arr{(Is, A{b, c})...}
  {}

  std::array<A, N> arr;
};

Live

Note if the move and copy constructors are deleted, this will only work after C++17.

Passer By
  • 19,325
  • 6
  • 49
  • 96
  • 1
    it's working on gcc arm. it's a multi-platform lib so if there was a < c++17 solution that'd have also been appreciated – Alex Darsonik Mar 16 '18 at 10:52
  • 1
    @AlexDarsonik The limitation on C++17 is only if `A` has deleted move and copy constructors. I haven't figured out a way around that. If you own `A`, you could also have it take a `std::pair` as argument and instead expand on `std::pair` – Passer By Mar 16 '18 at 10:53
  • I'd keep it deleted because it's a complex object. if I'll bump into a platform that doesn't have c++17 I'll just write the copy and move ctors – Alex Darsonik Mar 16 '18 at 10:58
  • ps: gcc adviced to static_cast(Is) in the template ctor of B – Alex Darsonik Mar 16 '18 at 11:07
4

For both C++11 and C++14 (i.e.: pre-C++17) what you want can be achieved by means of template metaprogramming.

You could declare the following helper class template, array_maker<>, which has a static member function template, make_array, that calls itself recursively:

template<typename T, std::size_t N, std::size_t Idx = N>
struct array_maker {
    template<typename... Ts>
    static std::array<T, N> make_array(const T& v, Ts...tail) {
        return array_maker<T, N, Idx-1>::make_array(v, v, tail...);
    }
};

Then, specialize this class template for the case Idx equal to 1, i.e.: the base case of the recursion:

template<typename T, std::size_t N>
struct array_maker<T, N, 1> {
    template<typename... Ts>
    static std::array<T, N> make_array(const T& v, Ts... tail) {
        return std::array<T, N>{v, tail...};
    }
};

Finally, it can be used in the constructor of your template this way:

template <size_t NR_A>
struct B {
  B (int b, int c) : mAs{array_maker<A, NR_A>::make_array(A{b,c})}
  {}    
  std::array<A, NR_A> mAs;
};
JFMR
  • 23,265
  • 4
  • 52
  • 76
  • 1
    I have chosen this as the correct answer because it achieves the same without C++17. v nice! – Alex Darsonik Mar 16 '18 at 13:52
  • hmm this requires the copy ctor to be available, though – Alex Darsonik Mar 16 '18 at 15:14
  • Prior to C++17 it is not possible to return a unnamed object that has neither *copy* nor *move* constructor, since *RVO* is an *optional optimization* in both C++11 and C++14. In C++17, the *copy elision* is however mandatory. – JFMR Mar 16 '18 at 15:30
1

here's a solution I came up with (requires c++17)

template<typename T, std::size_t N, std::size_t index_t = N, typename... Ts>
constexpr auto make_array(T t, Ts... ts)
{
  if constexpr (index_t <= 1) {
    return std::array<T, N> {t, ts...};
  } else {
    return make_array<T, N, index_t - 1>(t, t, ts...);
  }
}

This is a modification of one of the previous solution that had an array maker struct. This does the same just in a more concise form.

It takes in a single item and keeps doubling down on that item until it reaches a depth of 1,then returns an array from the unfolding. I didn't realize that you a fold expression can be used even though no arguments are passed through it. Although I know this implicitly from things like printf.