C++20’s std::views::filter not filtering the view correctly

C++20’s std::views::filter not filtering the view correctly


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.

Share
Improve this question

3

2 Answers
2

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 << ' ';
}

Share
Improve this answer


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); }))
{
   // ...
}

Demo (use the implementation provided by P2502)

Share
Improve this answer



Leave a Reply

Your email address will not be published. Required fields are marked *