I'm wondering if there is a proper way to do this.
Given the example:
struct Test {
static std::array<unsigned, 123> data;
};
std::array<unsigned, 123> Test::data = {};
If I want to set all the elements in the data
to some value, 5 for instance.
I could do something like this:
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();
But then I'd be allocating an extra arrays worth of memory and doing a copy on it all, which doesn't seem ideal.
Another less memory intensive option would be something like:
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 = {};
But now I have additional overhead when constructing my Test struct.
Now I know you can initialize pod arrays like so: std::array<int, 3> a = {1, 2, 3};
but for N
sized arrays this would quickly become horrible to maintain.
Every solution I seem to come across just uses the fill
member function on the array class but always treats the arrays as if they are non static member variables, or it initializes them in main, both of these don't really help my situation.
Edit: I created this utility header that generalizes the 2nd option. #pragma once
#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;
}
}
This could probably be improved more, but it seems to work. Usage is as follows:
std::array<unsigned, 123> Test::data = Utils::create_filled_container<decltype(data)>(456);
which sets all elements to whatever you specify.
Edit2: So I did some more testing and it looks like calling a function to fill the array vs manually doing it doesn't always produce the same assembly.
I've setup a demo here!
Edit3: Changed up some stuff so it could be constexpr, heres its current form:
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;
}
which produces the same assembly now.
Initialize it with a constexpr function or lambda. No runtime code is generated.
#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";
}