С++ инициализирует/заполняет элементы статического члена std::array определенным значением

Мне интересно, есть ли правильный способ сделать это.

Учитывая пример:

struct Test {
    static std::array<unsigned, 123> data;
};

std::array<unsigned, 123> Test::data = {};

Если я хочу установить для всех элементов в data какое-то значение, например, 5.

Я мог бы сделать что-то вроде этого:

struct Test {
    static std::array<unsigned, 123> data;
private:
    static decltype(data) init_data_arr() {
        decltype(data) arr = {};
        arr.fill(5);
        return arr;
    }
};

std::array<unsigned, 123> Test::data = Test::init_data_arr();

Но тогда я бы выделил дополнительные массивы памяти и сделал бы копию всего этого, что не кажется идеальным.

Другим менее интенсивным вариантом памяти будет что-то вроде:

struct Test {
    static bool is_init;
    static std::array<unsigned, 123> data;

    Test() {
        if (!is_init) {
            is_init = true;
            data.fill(5);
        }
    }
};

bool Test::is_init = false;
std::array<unsigned, 123> Test::data = {};

Но теперь у меня есть дополнительные накладные расходы при построении моей тестовой структуры.

Теперь я знаю, что вы можете инициализировать массивы pod следующим образом: std::array<int, 3> a = {1, 2, 3}; но для массивов размером N это быстро стало бы ужасно поддерживать.

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

Изменить: я создал этот заголовок утилиты, который обобщает второй вариант. #прагма один раз

#include <utility>
#include <iterator>
#include <algorithm>
#include <type_traits>

namespace Utils {

    template<typename Container>
    using element_type_t = std::remove_reference_t<decltype(*std::begin(std::declval<Container&>()))>;

    template<class Container>
    static Container create_filled_container(const element_type_t<Container>& value) {
        Container container = {};
        std::fill(container.begin(), container.end(), std::move(value));
        return container;
    }

}

Это, вероятно, можно было бы улучшить больше, но, похоже, это работает. Использование выглядит следующим образом:

std::array<unsigned, 123> Test::data = Utils::create_filled_container<decltype(data)>(456);

который устанавливает для всех элементов все, что вы укажете.

Edit2: Итак, я провел еще несколько тестов, и похоже, что вызов функции для заполнения массива по сравнению с ручным выполнением не всегда дает одну и ту же сборку.

Я установил демо здесь!

Edit3: кое-что изменилось, чтобы это могло быть constexpr, вот его текущая форма:

template<class Container>
static constexpr Container create_filled_container(const element_type_t<Container>& value) {
    Container container = {};

    for (auto it = container.begin(); it != container.end(); ++it)
        *it = value;

    return container;
}

который производит ту же сборку сейчас.


person Hex Crown    schedule 20.06.2019    source источник
comment
Но тогда я бы выделил дополнительный массив памяти и сделал бы копию всего этого, что не кажется идеальным. Вы уверены в этом? NRVO избавит вас от этого и ничего не будет стоить вам дополнительно. Вы должны проверить сборку, чтобы увидеть, делает ли она это.   -  person NathanOliver    schedule 20.06.2019
comment
@NathanOliver хороший звонок, я забыл включить оптимизацию, когда тестировал генерацию сборки. Но вы правы, он создает ту же сборку, когда -O3 используется для Test::init_data_arr() примера по сравнению с инициализацией {0, ..., n}.   -  person Hex Crown    schedule 20.06.2019
comment
@NathanOliver Итак, вы бы сказали, что это, вероятно, был бы лучший способ сделать это, или есть более элегантное решение для этого?   -  person Hex Crown    schedule 20.06.2019
comment
Это было бы так, как я бы это сделал. Его просто понять, упростить обслуживание, и он должен компилироваться. После того, как код будет готов, если вы недовольны производительностью, создайте профиль и посмотрите, в чем проблема. Этого скорее всего не будет.   -  person NathanOliver    schedule 20.06.2019
comment
@NathanOliver Итак, проведя дополнительное тестирование, я обнаружил, что даже при оптимизации -O3, в зависимости от типа данных, например char/float/int, сборка, созданная функцией заполненного массива, может значительно измениться из-за подробного arr = {1, 2,3,...,n} инициализация.   -  person Hex Crown    schedule 20.06.2019
comment
просто инициализируйте его с помощью функции constexpr. Нет кода времени выполнения.   -  person doug    schedule 20.06.2019


Ответы (1)


Инициализируйте его с помощью функции constexpr или лямбда. Код времени выполнения не генерируется.

#include <array>
#include <iostream>

template <unsigned len, unsigned val>
constexpr std::array<unsigned, len> initialize_array()
{
    std::array<unsigned, len> ret{};
    for (int i = 0; i < len; i++)
        ret[i] = val;
    return ret;
}

struct Test {
    constexpr static std::array<unsigned, 123> data{ initialize_array<123,5>() };
};

int main() {
    std::cout << Test::data[4] << "\n";
}
person doug    schedule 20.06.2019
comment
Однако data ОП даже не const. (может быть просто начальным значением, так как константный массив 5 в основном бесполезен). - person Jarod42; 20.06.2019
comment
@ Jarod42 Jarod42 Правда, для неконстантной памяти необходимо встроить массив. Это неявно для constexpr. Генерирует ли компилятор инициализированные данные или нет, в обоих случаях, к сожалению, не указано. Но может. Как правило, такие виды инициализации лучше всего подходят для таблиц констант поиска. Я нашел их полезными и эффективными в таких вещах, как арифметика Галуа для исправления ошибок и тому подобное, и видел впечатляющие оптимизации. Со временем компиляторы C++ стали лучше, и это, вероятно, будет продолжаться. - person doug; 21.06.2019
comment
@doug, генерирует ли компилятор инициализированные данные или нет, в любом случае не указано, я на самом деле этого не осознавал, я думал, что это должно происходить до статической инициализации, но я думаю, вы правы, они говорят только, что это должно происходить как если это произошло до статической инициализации. en.cppreference.com/w/cpp/language/constant_initialization - person Chris Beck; 27.08.2019