4

I am a complete beginner with the range-v3 library.. Suppose I want to fill a std::array with random numbers in some interval.

With iterators, I'd do something like this answer, passing iterators to my std::array in question as arguments.

template< class Iter >
void fill_with_random_int_values( Iter start, Iter end, int min, int max)
{
    static std::random_device rd;    // you only need to initialize it once
    static std::mt19937 mte(rd());   // this is a relative big object to create

    std::uniform_int_distribution<int> dist(min, max);

    std::generate(start, end, [&] () { return dist(mte); });
}

With the ranges library, I wanted to use ranges::view::generate_n, with a unary function that generates a single random number along with the size of my array.

auto random_num() -> int {
  static std::mt19937 engine{std::random_device{}()};
  static std::uniform_int_distribution<int> dist(1, 10);
  return dist(engine);
}

std::vector<int> nums = ranges::view::generate_n(random_num, 10);

This works well for a std::vector, but I am rather lost on what algorithm I should be using to fill a std::array rather than generate a std::vector, as a similar approach to above does not work. I can transform the array and disregard each argument but that doesn't seem right.

Eric Hansen
  • 1,749
  • 2
  • 19
  • 39
  • See also [How to construct an std::array with index sequence?](https://stackoverflow.com/q/41660062/636019) – ildjarn Apr 13 '19 at 03:14

3 Answers3

6

std::array is an aggregate; it has no user-provided constructors. As such, it has no constructors that create the object from a range.

You also can't (in C++17) write a function that takes a range and returns an array. The reason being that parameters are not constexpr, and the size of an array must be a constant expression. C++20 looks to be adding the ability to take more types as non-type template parameters, so it should be possible to do this as a template parameter. The code would look like:

template<auto rng>
    requires std::ranges::sized_range<decltype(rng)>
constexpr auto array_from_range()
{
  std::array<std::iter_value_t<decltype(rng)>, std::ranges::size(rng)> ret;
  std::ranges::copy(rng, ret);
  return ret;
}

Of course, this requires that the range itself is constexpr, not merely its size.

Guss
  • 762
  • 4
  • 20
Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
5

The size of a std::array is a compile-time constant so I can't see how the library can generate one for you using a runtime argument.

Here's a fairly trivial implementation which I think does what you need:

#include <random>
#include <array>
#include <utility>
#include <iostream>

auto random_num() -> int {
  static std::mt19937 engine{std::random_device{}()};
  static std::uniform_int_distribution<int> dist(1, 10);
  return dist(engine);
}

template<class F, std::size_t...Is>
auto generate_array_impl(F&& f, std::index_sequence<Is...>) -> std::array<decltype(f()), sizeof...(Is)>
{
    return std::array<decltype(f()), sizeof...(Is)>
    {{
        (void(Is), f())...
    }};
}

template<std::size_t N, class F>
auto generate_array(F f) -> std::array<decltype(f()), N>
{
    return generate_array_impl(f, std::make_index_sequence<N>());
}


int main()
{
    auto arr = generate_array<10>(random_num);
    for (auto x : arr)
    std::cout << x << '\n';
}

https://coliru.stacked-crooked.com/a/983064b89c4dd355

Or adding some constexpr magic...

#include <random>
#include <array>
#include <utility>
#include <iostream>
#include <boost/range.hpp>

template<std::size_t N>
constexpr auto c_size_t = std::integral_constant<std::size_t, N>();

auto random_num() -> int {
  static std::mt19937 engine{std::random_device{}()};
  static std::uniform_int_distribution<int> dist(1, 10);
  return dist(engine);
}

template<class F, std::size_t...Is>
constexpr auto generate_array_impl(F&& f, std::index_sequence<Is...>) -> std::array<decltype(f()), sizeof...(Is)>
{
    return std::array<decltype(f()), sizeof...(Is)>
    {{
        (void(Is), f())...
    }};
}

template<std::size_t N, class F>
constexpr auto generate_array(F&& f, std::integral_constant<std::size_t, N>) -> std::array<decltype(f()), N>
{
    return generate_array_impl(f, std::make_index_sequence<N>());
}

int main()
{
    auto arr = generate_array(random_num, c_size_t<10>);
    for (auto x : arr)
        std::cout << x << ',';
    std::cout << '\n';

    constexpr auto arr2 = generate_array([i = std::size_t(0)]() mutable { return i++; }, c_size_t<10>);
    for (auto x : arr2)
        std::cout << x << ',';
    std::cout << '\n';
}

https://coliru.stacked-crooked.com/a/42c9c011026779eb

Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
2

Since the size of the array is a compile-time constant. You have to construct it by yourself. You can fill it then like

std::array<int, 10> arr;
ranges::generate(arr, random_num);
sv90
  • 522
  • 5
  • 11