C++11 std::generate and std::uniform_real_distribu

2019-08-24 22:33发布

问题:

calling std::generate algorithm from the STL two times on different containers produces equivalent results.

Consider I want to fill up two float arrays with random numbers between -1. and 1. :

std::array<float, 1000> x;
std::array<float, 1000> y;

std::random_device rd;
std::mt19937_64 gen(rd());
std::uniform_real_distribution<float> dis(-1.f, 1.f);
auto rand = std::bind(dis, gen);
std::generate(x.begin(), x.end(), rand);
std::generate(y.begin(), y.end(), rand);

You can test it here : http://ideone.com/X712IU. Both arrays are filled up with the exact same values :

0:  -0.411968,  -0.411968
1:    0.55158,    0.55158
2:    0.69889,    0.69889
3:  -0.901328,  -0.901328
4:  -0.556142,  -0.556142
5:  -0.798431,  -0.798431
6:  -0.570874,  -0.570874
7:   0.928999,   0.928999
8:   0.118056,   0.118056
9:  -0.655123,  -0.655123

Now if I make a new generator between the generates it works ok :

std::array<float, 1000> x;
std::array<float, 1000> y;

// Generators in different scopes, OK
std::random_device rd;
{
  std::mt19937_64 gen(rd());
  std::uniform_real_distribution<float> dis(-1.f, 1.f);
  auto rand = std::bind(dis, gen);
  std::generate(x.begin(), x.end(), rand);
}
{
  std::mt19937_64 gen(rd());
  std::uniform_real_distribution<float> dis(-1.f, 1.f);
  auto rand = std::bind(dis, gen);
  std::generate(y.begin(), y.end(), rand);
}

Gives :

0:   0.391496,   -0.64993
1:   0.429592,   0.835015
2: 0.00735116,    0.77657
3:  -0.548355, -0.0794801
4:  -0.312095,  -0.119841
5:   0.931296,   0.997449
6:  -0.934924,  -0.832223
7:   0.432267,   0.181224
8:   0.942709,   0.165024
9:   0.315852,  -0.654576

And now with the same generator using for loops, it works as well :

// Both arrays assigned in the same loop, OK
for(size_t i = 0; i < x.size(); ++i)
{
  x[i] = rand();
  y[i] = rand();
}

// Arrays in separated loops, OK
for(size_t i = 0; i < x.size(); ++i)
  x[i] = rand();

for(size_t i = 0; i < y.size(); ++i)
  y[i] = rand();

It looks like something related to std::generate, but I can't find out what would cause this behavior.

Any explanation ? Cheers

EDIT:

As pointed out by dotcavader, the issue comes simply because the generator object has been copied (both by std::bind and std::generate). Hence the generator start with the exact same internal state for both generate.

Just to complete the answer, Praetorian and Casey gave two easy solutions for this :

Using std::reference_wrapper to store a reference in the bind instead of a copy :

auto rand = std::bind(dis, std::ref(gen));

Using a lambda that returns a reference :

auto rand = [&](){ return dis(gen); };

回答1:

I believe what's happening here is that std::generate takes its generator argument by value. As soon as you pass it in, it's copied. That means what happens inside std::generate does not affect the function object you passed in.

So you call generate again, and you copy the same generator function object, which then starts generating with exactly the same state including (somewhere inside there) which ever seed was used to initialise the number generation.

If you create a DIFFERENT generator function, even with the same parameters, you must get a different random seed inside of it. This is how your other methods are generating different results. Ultimately, their generators start with a different seed.