Initialization of member array with noncopyable non pod

161 views Asked by At

I think the most simple way to ask is due to an example. Assume we have the following type:

class Node
{
  // make noncopyable
  Node(const Node& ref) = delete;      
  Node& operator=(const Node& ref) = delete;

  // but moveable
  Node(Node&& ref) = default;
  Node& operator=(Node&& ref) = default;

  // we do not have a default construction
  Node() = delete;
  Node(unsigned i): _i(i) {}

  unsigned _i;
};

Now i want to store some of these Nodes in a std::array:

template<unsigned count>
class ParentNode
{
  std::array<Node,count> _children;
  ParentNode() 
     // i cannt do this, since i do not know how many nodes i need
     // : _children{{Node(1),Node(2),Node(3)}}  
     : _children()  // how do i do this?
  {}
};

As stated in the comment, the question is: How do I do this? The unsigned passed to the child should be the index of the array where the child is stored. But more general solutions are also very appreciated!

The following solution I found myself might end up in undefined behavior for more complex types. For a proper well defined solution see accepted answer.

template<unsigned count>
class ParentNode
{
public:
   // return by value as this will implicitly invoke the move operator/constructor
   std::array<Node,count> generateChildren(std::array<Node,count>& childs)
   {
      for (unsigned u = 0; u < count; u++)
         childs[u] = Node(u);  // use move semantics, (correct?)

      return std::move(childs); // not needed
      return childs;  // return by value is same as return std::move(childs)
   }

  std::array<Node,count> _children;

  ParentNode() 
     // i cannt do this, since i do not know how many nodes i need
     // : _children{{Node(1),Node(2),Node(3)}}  
     : _children(generateChildren(_children))  // works because of move semantics (?)
  {}
};

ParentNode<5> f; 

The code does compile. But I am not sure if it does what i expect it to do. Maybe someone who has good insight in move semantics and rvalue references can just add some comments:-)

2

There are 2 answers

4
Casey On BEST ANSWER

You can use a variadics to generate an array with elements initialized to an arbitrary function of the indices. Using the standard machinery for generating index sequences:

template <int... I> struct indices {};
template <int N, int... I> struct make_indices :
  make_indices<N-1,N-1,I...> {};
template <int... I> struct make_indices<0,I...> : indices<I...> {};

it's fairly straightforward:

template <typename T, typename F, int... I>
inline std::array<T, sizeof...(I)> array_maker(F&& f, indices<I...>) {
  return std::array<T, sizeof...(I)>{ std::forward<F>(f)(I)... };
}

template <typename T, std::size_t N, typename F>
inline std::array<T, N> array_maker(F&& f) {
  return array_maker<T>(std::forward<F>(f), make_indices<N>());
}

Which lets us do anything from duplicating the effect of std::iota:

auto a = array_maker<int,10>([](int i){return i;});

to making an array with the squares of the first 10 natural numbers in reverse order:

const auto a = array_maker<std::string,10>([](int i){
  return std::to_string((10 - i) * (10 - i));
});

Since your Node is movable, this allows you to define your ParentNode constructor as:

ParentNode() 
   : _children(array_maker<Node, count>([](unsigned i){return i+1;}))
{}

See it all put together live at Coliru.

1
Nicol Bolas On

Really, there's not much you can do. You painted yourself into a corner by wanting to put a type with no default constructor into an array of a size determined by a template parameter, and then wanting to initialize the elements with some arbitrary values.

There is nothing you can return from a function which can be put into a braced-init-list and used to initialize an array (or aggregate of any kind) with more than one element. {} doesn't mean "initializer_list". It's a braced-init-list, which can become an initializer_list under certain circumstances, but it can also become the parameters for a constructor call or the elements to use in aggregate initialization.

Your best bet is really to just use a vector and initialize it manually with a loop.