Заполните std :: array типом, не являющимся конструктивным по умолчанию (без вариативных шаблонов)

Предположим, у меня есть тип A без конструктора по умолчанию:

struct A
{
  int x;
  A(int x) : x(x) {}
};

Я хочу сделать std::array из A. Я могу легко сделать это с помощью списка инициализаторов:

std::array<A, 5> arr = { 0, 1, 4, 9, 16 };

Здесь вы можете увидеть закономерность. Да, у меня может быть функция генератора для вычисления каждого значения массива:

int makeElement(size_t i)
{
  return i * i;
}

std::array<A, 5> arr = { 
  makeElement(0), 
  makeElement(1),
  makeElement(2),
  makeElement(3),
  makeElement(4)
};

И да, на самом деле у меня гораздо больше 5 элементов (а именно 64). Так что было бы неплохо не повторять makeElement 64 раза. Единственное решение, которое я придумал, - использовать вариативные шаблоны для распаковки пакета параметров в список инициализаторов: https://ideone.com/yEWZVq (также проверяет, правильно ли удалены все копии). Это решение было вдохновлено этим вопросом < / а>.

Это работает, но я бы хотел не злоупотреблять вариативными шаблонами для такой простой задачи. Вы знаете, раздувание исполняемого файла, замедление компиляции и все такое. Я хотел бы сделать что-то вроде этого:

  1. Создайте неинициализированное хранилище подходящего размера
  2. Инициализируйте все элементы в цикле с размещением new
  3. Волшебным образом преобразуйте хранилище в std::array и верните его

Я могу сделать несколько грязных хаков, чтобы реализовать это в динамической памяти: https://ideone.com/tbw5lm Но это не лучше std::vector, где у меня вообще таких проблем нет.

И я понятия не имею, как я могу это сделать в автоматической памяти. Т.е. иметь ту же удобную функцию, возвращающую std::array по значению, со всем этим скрытым за капотом. Любые идеи?

Полагаю, что boost::container::static_vector может быть для меня хорошим решением. К сожалению, я не могу использовать boost для этой конкретной задачи.

PS. Обратите внимание, что этот вопрос больше похож на исследовательский интерес. В реальном мире и вариативные шаблоны, и std::vector будут работать нормально. Я просто хочу знать, может быть, я что-то упускаю.


person Mikhail    schedule 18.06.2016    source источник
comment
большой размер исполняемого файла Вы уверены в этом?   -  person Baum mit Augen    schedule 18.06.2016
comment
Если я сделаю это во многих местах, мне понадобится дополнительная функция для каждой такой инициализации. Не критичный пункт, но все же. Этот вопрос скорее исследовательский.   -  person Mikhail    schedule 18.06.2016
comment
Правильное использование генераторов constexpr не повлияет на размер исполняемого файла. Что касается замедления компиляции, что ж, добро пожаловать на C ++.   -  person Sam Varshavchik    schedule 18.06.2016
comment
@SamVarshavchik Не могли бы вы уточнить? Или предоставить демо? Я не уверен, что вы имеете в виду под генераторами constexpr.   -  person Mikhail    schedule 18.06.2016


Ответы (2)


Я думаю, ваши опасения по поводу раздувания кода неверно истолкованы. Вот образец:

#include <utility>
#include <array>

template<std::size_t... ix>
constexpr auto generate(std::index_sequence<ix...> ) {
    return std::array<int, sizeof...(ix)>{(ix * ix)...};
}

std::array<int, 3> check() {
 return generate(std::make_index_sequence<3>());
}

std::array<int, 5> glob = generate(std::make_index_sequence<5>());

Производит очень аккуратную сборку:

check():
        movl    $0, -24(%rsp)
        movl    $1, -20(%rsp)
        movl    $4, %edx
        movq    -24(%rsp), %rax
        ret
glob:
        .long   0
        .long   1
        .long   4
        .long   9
        .long   16

Как видите, раздувания кода не видно. Статический массив инициализируется статически, а для автоматического массива это просто набор ходов. И если вы думаете, что набор движений - это ужасное раздувание кода, считайте это развертыванием цикла - что всем нравится!

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

person SergeyA    schedule 18.06.2016
comment
Хорошее понимание, спасибо! Но что касается вашего последнего пункта, это не совсем так. Я разместил ссылку в описании, как это сделать с размещением new. - person Mikhail; 18.06.2016
comment
@Mikhail похоже, что вы решили использовать новое размещение. В этом случае вам, вероятно, придется исследовать std::aligned_storage, иначе любое созданное вами самодельное решение может дать сбой в тот момент, когда вы запустите его на чем-то, кроме Intel. - person Richard Hodges; 18.06.2016
comment
@RichardHodges, вы намерены использовать новое размещение, а я нет. Я упомянул об этом здесь только для того, чтобы показать, что утверждение, что нет другого соответствующего решения, является ложным. Так что могут быть другие решения. - person Mikhail; 18.06.2016
comment
@Mikhail, если ваш компилятор не старше 10 лет, я бы действительно не беспокоился о раздувании кода. Решение Сергея (почти) идеальное. - person Richard Hodges; 18.06.2016
comment
@RichardHodges Я согласен. Как я уже сказал в вопросе, это скорее исследовательский интерес. - person Mikhail; 18.06.2016
comment
@sergey Я добавляю +1 к вашему ответу и увеличиваю диапазон ;-) - person Richard Hodges; 18.06.2016
comment
@Mikhail, размещение новое тебе в std::array не поможет. Если вы спрашиваете о статическом хранилище в целом, это совсем другой вопрос, и, конечно, есть решение - вы сами упомянули boost::static_array и можете смоделировать на нем свою собственную вещь. Я имел в виду, что std::array не имеет места для размещения нового, предназначенного для каламбура. - person SergeyA; 18.06.2016

Вот еще один способ, который позволяет произвольный диапазон входных данных для генератора:

Вот пример использования:

/// generate an integer by multiplying the input by 2
/// this could just as easily be a lambda or function object
constexpr int my_generator(int x) {
    return 2 * x;
}

int main()
{
    // generate a std::array<int, 64> containing the values
    // 0 - 126 inclusive (the 64 acts like an end() iterator)
    static constexpr auto arr = generate_array(range<int, 0, 64>(),
                                               my_generator);

    std::copy(arr.begin(), arr.end(), std::ostream_iterator<int>(std::cout, ", "));
    std::cout << std::endl;
}

Вот шаблон, позволяющий ему работать

#include <utility>
#include <array>
#include <iostream>
#include <algorithm>
#include <iterator>

/// the concept of a class that holds a range of something
/// @requires T + 1 results in the next T
/// @requires Begin + 1 + 1 + 1.... eventually results in Tn == End
template<class T, T Begin, T End>
struct range
{
    constexpr T begin() const { return Begin; }
    constexpr T end() const { return End; }

    constexpr T size() const { return end() - begin(); }
    using type = T;
};

/// offset every integer in an integer sequence by a value
/// e.g offset(2, <1, 2, 3>) -> <3, 4, 5>
template<int Offset, int...Is>
constexpr auto offset(std::integer_sequence<int, Is...>)
{
    return std::integer_sequence<int, (Is + Offset)...>();
}

/// generate a std::array by calling Gen(I) for every I in Is
template<class T, class I, I...Is, class Gen>
constexpr auto generate_array(std::integer_sequence<I, Is...>, Gen gen)
{
    return std::array<T, sizeof...(Is)> {
        gen(Is)...
    };
}

/// generate a std::array by calling Gen (x) for every x in Range
template<class Range, class Gen>
constexpr auto generate_array(Range range, Gen&& gen)
{
    using T = decltype(gen(range.begin()));
    auto from_zero = std::make_integer_sequence<typename Range::type, range.size()>();
    auto indexes = offset<range.begin()>(from_zero);
    return generate_array<T>(indexes, std::forward<Gen>(gen));
}

/// generate an integer by multiplying the input by 2
constexpr int my_generator(int x) {
    return 2 * x;
}

int main()
{
    static constexpr auto arr = generate_array(range<int, 0, 64>(),
                                               my_generator);

    std::copy(arr.begin(), arr.end(), std::ostream_iterator<int>(std::cout, ", "));
    std::cout << std::endl;
}

вот раздутый код до сборки:

.LC0:
    .string ", "
main:
;; this is the start of the code that deals with the array
    pushq   %rbx
    movl    main::arr, %ebx
.L2:
    movl    (%rbx), %esi
    movl    std::cout, %edi
    addq    $4, %rbx
;; this is the end of it

;; all the rest of this stuff is to do with streaming values to cout
    call    std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
    movl    $2, %edx
    movl    $.LC0, %esi
    movl    std::cout, %edi
    call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
    cmpq    main::arr+256, %rbx
    jne     .L2
    movl    std::cout, %edi
    call    std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
    xorl    %eax, %eax
    popq    %rbx
    ret
    subq    $8, %rsp
    movl    std::__ioinit, %edi
    call    std::ios_base::Init::Init()
    movl    $__dso_handle, %edx
    movl    std::__ioinit, %esi
    movl    std::ios_base::Init::~Init(), %edi
    addq    $8, %rsp
    jmp     __cxa_atexit

main::arr:
    .long   0
    .long   2
    .long   4
    .long   6
    .long   8
    .long   10
    .long   12
    .long   14
    .long   16
    .long   18
    .long   20
    .long   22
    .long   24
    .long   26
    .long   28
    .long   30
    .long   32
    .long   34
    .long   36
    .long   38
    .long   40
    .long   42
    .long   44
    .long   46
    .long   48
    .long   50
    .long   52
    .long   54
    .long   56
    .long   58
    .long   60
    .long   62
    .long   64
    .long   66
    .long   68
    .long   70
    .long   72
    .long   74
    .long   76
    .long   78
    .long   80
    .long   82
    .long   84
    .long   86
    .long   88
    .long   90
    .long   92
    .long   94
    .long   96
    .long   98
    .long   100
    .long   102
    .long   104
    .long   106
    .long   108
    .long   110
    .long   112
    .long   114
    .long   116
    .long   118
    .long   120
    .long   122
    .long   124
    .long   126

то есть вообще ничего.

person Richard Hodges    schedule 18.06.2016