Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Idiom for initializing an std::array using a generator function taking the index?

Suppose I have a function T foo(size_t i). What would be an elegant and succinct way of constructing an object arr, of type std::array<T, N>, so that we have arr[i] == foo(i)?

If possible, I would like for this construction to work even when T is not a default-initializable type.

Notes:

  • Since T is not default-initializable, the code can't start with std::array<T, N> arr; and then some initialization.
  • The code must work for any value of N, generically.
like image 740
einpoklum Avatar asked Apr 22 '26 04:04

einpoklum


1 Answers

I suggest the idiom of writing a utility function template which does this for you, succinctly and clearly:

template<std::size_t N, typename Generator>
constexpr auto generate_array(Generator&& gen) 
-> std::array<decltype(gen(0), N>;

so, the generate_array function takes the generator as a parameter, and the number of elements to generate as a template-parameter.

Example of use

If you write:

auto gen = [](std::size_t i) { return  11*(i+1); };
auto arr = generate_array<5>(gen);
for (auto i : arr) { std::cout << i << ' ';}
std::cout << '\n';

the program will print:

11 22 33 44 55

and you can see this in action on GodBolt.

Implementation

We will use a helper functions involving some template metaprogramming voodoo known as the "indices trick":

namespace detail {

template<typename T, std::size_t... Is, typename Generator>
auto generate_array(std::index_sequence<Is...>, Generator&& gen)
-> std::array<T, sizeof...(Is)>
{
    return std::array<T, sizeof...(Is)>{ gen(Is) ... };
}

} // namespace detail

template<std::size_t N, typename Generator>
constexpr auto generate_array(Generator&& gen) ->
std::array<decltype(gen(0)), N>
{
    return detail::generate_array<decltype(gen(0))>(
        std::make_index_sequence<N>{}, std::forward<Generator>(gen));
}

the inner function (in detail::) has a template parameter pack of the same size as the intended array, so it can use pack expansion to get just the right number of elements you need for your output; the external function creates that pack.

Notes

  • The implementation is C++14; but only because of the use of std::index_sequence and its being constexpr; if you implement it yourself in C++11, you can adapt the generator function and have a pure-C++11 solution.
  • If you can rely on C++20 or C++23, the solution can be more terse and not require the auxiliary detail::generate_array() function template, as @Jarod42 points out. This is explicitly demonstrated in @TobySpeight's answer.
  • As @WeijunZhou notes, this solution is limited by the compilers ability to expand template parameter packs. So, an N of 500,000 may well fail.
  • This utility function can be thought of as generalizing this one, in an answer by @Jarod42, which creates an std::array of constant values.
  • Thanks to @TobySpeight for pointing out it's better to make sure we pass a size_t (in case the generator behaves differently on int's and on size_t's - which it might), and that the function can be marked constexpr.
like image 54
einpoklum Avatar answered Apr 24 '26 19:04

einpoklum



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!