Построить std::array, заполнив одним элементом

У меня есть класс списка, в котором переменная размера является членом const. Это полезно для меня, потому что применяет требование, согласно которому размер списка может варьироваться от запуска к запуску, но не может изменяться в рамках отдельного запуска.

Я хотел бы создать коллекцию этих списков. Количество списков в коллекции является переменной шаблона, поэтому я хотел бы использовать std::array... т.е. мне нужен массив списков, где размер массива является параметром шаблона, а размер каждого list - это const, указанный при построении

К сожалению:

  • Список const-size не имеет конструктора по умолчанию (его размер должен быть указан!), поэтому мне нужно указать аргумент конструктора для каждого элемента списка. Я не могу просто создать массив, а затем установить элементы
  • Поскольку размер моего списка является переменной шаблона, я не могу использовать стандартный список инициализаторов — количество требуемых элементов варьируется.

Я понимаю, что есть альтернативы:

  • Я мог бы использовать std::vector и просто push_back элементы один за другим, пока размер вектора не сравняется с параметром моего шаблона, но это кажется неэлегантным, потому что это не будет естественным образом обеспечивать условие, согласно которому размер результирующего вектора не должен изменяться после он полностью заселен.
  • Я мог бы изменить порядок индексов и получить список std::arrays константного размера. Однако это не очень хорошо сочетается с остальной частью моего кода; Я хотел бы иметь возможность передавать отдельный список константного размера из массива в клиентский код.
  • Я мог бы создать конструктор по умолчанию для класса списка постоянного размера, создать массив, а затем использовать новое размещение для замены элементов массива один за другим. Похоже, это может иметь некоторые плохие побочные эффекты (что делает конструктор по умолчанию для списка const-size? Что, если он случайно вызывается в другом месте? Что происходит, когда мой преемник понятия не имеет, что я сделал?)

Поскольку ни один из них не является полностью идеальным, я думаю, что было бы здорово, если бы существовал конструктор массива (или вспомогательная функция), который принимал бы в качестве аргументов:

  1. Количество элементов в массиве T
  2. Один объект T

... и вернуть std::array<T>, где каждый T был создан методом копирования из аргумента 2.

Существует ли такая вещь?


person user1476176    schedule 03.04.2016    source источник
comment
размер каждого списка является константой, указанной при построении - я искренне надеюсь, что вы также имеете в виду время компиляции, верно? В противном случае вы можете отказаться от использования std::array<T,N> с самого начала.   -  person WhozCraig    schedule 03.04.2016
comment
Размер списков не является константой времени компиляции.   -  person user1476176    schedule 03.04.2016


Ответы (2)


Ok. Магия шаблонов. Поскольку std::array является агрегатным типом, его можно инициализировать с помощью агрегатной инициализации:

std::array<T, 5> arr = { one, two, three, four, five };

Идея заключается в том, что one,..., five - это пять копий сконструированного объекта типа T (список в вашем случае), использующих пользовательский ввод в качестве параметра. Итак, давайте играть. Не смейтесь и не плачьте, прочитав это:

Идея состоит в том, чтобы взять один объект типа T и реплицировать его пять раз:

 { T(param), .... }; // Five repetitions.

Итак, давайте создадим функцию, которая возвращает уже инициализированный массив:

std::array<A, 5> arr = create_array_by_copy<5>(A(10));

Эта функция вернет array<A, 5> с 5 копиями этого временного объекта.

Для этого мы будем использовать вспомогательный struct, который создаст пакет параметров длиной 5 (параметризованный как s):

template<std::size_t s, class... voids_t>
struct sized_pack : public sized_pack<s - 1, voids_t..., void>
{};

template<class... voids_t>
struct sized_pack<0, voids_t...>
{};

Это создаст пакет параметров с именем voids_t, который представляет собой просто список s voids. А теперь суть трюка:

template<std::size_t s, class T, class... pack_t>
std::array<T, s>
create_array_by_copy_helper(sized_pack<0, pack_t...> const&,
                            T const& o)
{ return { (pack_t(), o)... }; }

template<std::size_t s, class T>
std::array<T, s> create_array_by_copy(T const& o)
{
    return create_array_by_copy_helper<s>(sized_pack<s>(), o);
}

Это сложно. Я знаю... поскольку мы передали объект типа sized_pack<s> вспомогательной функции, этот временный объект создаст экземпляр иерархии sized_pack, последний базовый класс которого будет объектом типа sized_pack<0, void, void, void, void, void>.

Функция apply получит этот объект как ссылку на size_pack<0, pack_t...> (последний базовый класс, обратите внимание на первый 0), поэтому pack_t будет нашим списком из 5 void.

Наконец, мы имеем:

 (pack_t(), o)

который является просто оператором запятой, поэтому он возвращает o. Идея состоит в том, что мы вставили pack_t (пакет параметров) внутрь «шаблона», поэтому при применении ... к выражению оно будет заменено выражениями, разделенными запятыми, где каждое появление pack_t будет заменено каждым элементом в пакет параметров в том же порядке, поэтому:

  { (pack_t(), o)... }

трансформируется в:

  { (void(), o), (void(), o), (void(), o), (void(), o), (void(), o) }

список инициализации!! Наконец, каждый элемент представляет собой просто void выражений, за которыми следует оператор запятой, и только второй элемент каждой пары будет возвращен оператором запятой. Итак, оцениваемое выражение будет:

  return { o, o, o, o, o }; // With its corresponding calls to `forward`.

Наш желаемый список инициализации!!

Пример Колиру:

http://coliru.stacked-crooked.com/a/d6c4ab6c9b203130

Вам нужно только заменить тип T вашим классом списка.

person Peregring-lk    schedule 03.04.2016
comment
Почему вы заново изобретаете std::make_integer_sequence? - person T.C.; 03.04.2016
comment
Хорошо, я не знал, что он существует. Во всяком случае, это функция С++ 14, и некоторые версии компилятора, которые все еще используются сегодня (например, g++ 4.8.4), не имеют полной поддержки С++ 14. - person Peregring-lk; 03.04.2016
comment
Прохладный! Я принимаю этот ответ, но вам также может быть интересно увидеть ответ, который кто-то дал на предыдущее воплощение моего вопроса: stackoverflow.com/questions/18497122/ - person user1476176; 03.04.2016
comment
В моем ответе была ошибка. Объект должен быть передан копией, потому что вы не можете переместить один и тот же объект пять раз. Это слишком опасно. - person Peregring-lk; 03.04.2016
comment
И я упростил решение, хотя знаю, что его можно упростить еще больше. - person Peregring-lk; 03.04.2016

std::array<T, N> — это класс-шаблон, представляющий массив длиной N элементов типа T. Если вы хотите создать массив списков, вы можете просто сделать std::array<std::list<T>>, N>, где N известно во время во время компиляции. const std::size_t N недостаточно, он должен быть constexpr.

Так что нет, вы не можете этого сделать. Однако вы можете использовать для этого std::vector.

Если вы опубликуете код своих усилий, мы сможем придумать что-нибудь получше.

person Zereges    schedule 03.04.2016
comment
Спасибо! Я думаю, что, возможно, проще всего просто сохранить std::array указателей на списки постоянного размера, а затем создать списки соответствующего размера с помощью new(). Мне нужно беспокоиться о создании новых и удалении, но размер гарантирует, что я ищу возможные варианты. - person user1476176; 03.04.2016
comment
@user1476176 user1476176 Используйте интеллектуальный указатель, он сам обрабатывает выделение/освобождение памяти. - person Zereges; 03.04.2016