7
I was writing a simple C++ program that generates a list of random integer values from a normal distribution, then takes first N generated items and filters them so that their absolute value is greater than 42. The code i wrote is the following:
int main() {
//convenience
namespace rng = std::ranges;
namespace view = std::views;
//random generation boilerplate
std::random_device r;
std::mt19937 gen(r());
std::normal_distribution<float> taps(-4, 15);
//the filtering
for (const auto& value : view::repeat(0)
| view::transform([&taps, &gen](auto _) { return std::round(taps(gen)); })
| view::take(1005)
| view::filter([](auto val) { return (val < -42 || val > 42); }))
{
std::cout << value << ' ';
}
return 0;
}
if i remove the view::filter
line, the sequence seems to be perfectly normal and there are about 3 or so values that satisfy the condition. However, when the filter is applied, the output seems to be some random trash, like -12 -8 -14 2 -4 -11 -38
. How does that make any sense? These numbers don’t even satisfy the filtering condition. Any help is appreciated.
notes:
compiled with g++ -o test main.cpp -std=c++2b
, both on Windows and Linux.
If i replace views::repeat(0) with views::iota(0) and compile with -std=c++20
, the output is the same – some random garbage.
Also, if someone suggests the better way to generate list from lambda, I would appreciate this a lot.
3
2 Answers
Reset to default
5
std::views::transform
requires the function object to be regular_invocable
, which means repeated calls must produce the same value; otherwise the behavior is undefined.
In range-v3 there are views::generate
and views::generate_n
for generating a sequence from repeated function calls, but it’s not included in the standard library (yet).
For now, it might be easier to use a non-lazy algorithm.
std::vector<int> values;
std::ranges::generate_n(std::back_inserter(values), 1005,
[&taps, &gen] { return std::round(taps(gen)); });
for (const auto& value : values
| view::filter([](auto val) { return (val < -42 || val > 42); }))
{
std::cout << value << ' ';
}
3
As a supplement to the answer, in C++23 you can easily construct a random number generator view using std::generator
, which eliminates the need for an extra container to store random numbers
#include <generator>
std::generator<int> generate(auto gen) {
for (;;)
co_yield gen();
}
then you can use it like
auto gen = [] {
static std::random_device r;
static std::mt19937 gen(r());
static std::normal_distribution<float> taps(-4, 15);
return taps(gen);
};
for (const auto& value : generate(gen)
| view::take(1005)
| view::filter([](auto val) { return (val < -42 || val > 42); }))
{
// ...
}
These are "lazy" view, so your
transform
to random is done each time you deference that iterator.10 hours ago
Oh, thanks, didn't consider that. I added output to transform function, and it outputted for each take and successful filter.
10 hours ago
Related question: Why C++ ranges "transform -> filter" calls transform twice for values that match the filter's predicate?
1 hour ago