Инициализация массива-члена с некопируемым non pod

Я думаю, что самый простой способ задать вопрос - это пример. Предположим, у нас есть следующий тип:

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;
};

Теперь я хочу сохранить некоторые из этих узлов в массиве std:::

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?
  {}
};

Как указано в комментарии, вопрос заключается в следующем: как мне это сделать? Беззнаковое значение, передаваемое дочернему элементу, должно быть индексом массива, в котором хранится дочерний элемент. Но более общие решения также очень ценятся!

Следующее решение, которое я нашел, может привести к неопределенному поведению для более сложных типов. Правильно определенное решение см. В принятом ответе.

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; 

Код компилируется. Но я не уверен, что он делает то, что я ожидаю. Может быть, кто-то, кто хорошо разбирается в семантике перемещения и ссылках на rvalue, может просто добавить некоторые комментарии:-)

2 ответа

Решение

Вы можете использовать переменную для генерации array с элементами, инициализированными к произвольной функции индексов. Использование стандартного механизма для генерации индексных последовательностей:

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

это довольно просто:

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>());
}

Что позволяет нам делать что-либо от дублирования эффекта std::iota:

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

сделать массив с квадратами первых 10 натуральных чисел в обратном порядке:

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

Так как ваш Node подвижный, это позволяет вам определить ParentNode конструктор как:

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

Посмотрите все это вместе в прямом эфире в Coliru.

На самом деле, вы мало что можете сделать. Вы загнали себя в угол, желая поместить тип без конструктора по умолчанию в массив размера, определенного параметром шаблона, а затем захотели инициализировать элементы с некоторыми произвольными значениями.

Вы ничего не можете вернуть из функции, которая может быть помещена в фигурный список инициализации и использована для инициализации массива (или любого другого агрегата) с более чем одним элементом. {} не значит "initializer_listMsgstr "Это ограниченный список инициализации, который может стать initializer_list при определенных обстоятельствах, но он также может стать параметрами для вызова конструктора или элементами, которые будут использоваться при инициализации агрегата.

Ваша лучшая ставка на самом деле просто использовать vector и инициализировать его вручную с помощью цикла.

Другие вопросы по тегам