5

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.

1

3 Answers 3

7

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.

Sign up to request clarification or add additional context in comments.

Comments

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

Comments

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);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.