4

I'm running through C++ Primer and I just completed the chapter disussing the <random> utilities. A few questions ask me:

Exercise 17.28: Write a function that generates and returns a uniformly distributed random unsigned int each time it is called.

Exercise 17.29: Allow the user to supply a seed as an optional argument to the function you wrote in the previous exercise.

Exercise 17.30: Revise your function again this time to take a minimum and maximum value for the numbers that the function should return.

These questions want me to use the discussed random_default_engine and uniform_int_distribution classes. The first question is simple enough:

#include <random>
unsigned randomUns(){
    static default_random_engine e;
    static uniform_int_distribution<unsigned> u;
    return u(e);
}

Using the suggestion that the objects are made static so that they aren't destroyed after a call, and so every call to the function generates a new number rather than starting at the beginning of the sequence every time. The difficulty comes in the last two questions, my solution originally was:

unsigned randomUns(unsigned minV, unsigned maxV, default_random_engine::result_type seed = 0){
    static default_random_engine e(seed);
    static uniform_int_distribution<unsigned> u(minV, maxV);
    return u(e);
}

Except that was before I realised that static variables only get initialised once. So this function would initialise u with the arguments from the call to randomUns and then never again. I would only be able to use this function with one range. E.g.

cout << randomUns(2,3) << " " << randomUns(5,6);

might output 2 3 despite the second range being different.

A similar problem exists with seed. How would I approach this? For the engine/seed I might be able to rewrite the function like so:

static default_random_engine::result prev = 0;
static default_random_engine e(seed);
if(prev != seed){ 
    e.seed(seed)
    prev = seed;
}

But of course this wouldn't be a truly random function.

randomUns(1,2,50);
randomUns(1,2,60);
randomUns(1,2,50);

as I know that third call will produce the same output as the first, since the engine got reset back to the same state. For changing the range on the distribution I'm not sure about at all.

The variables can't be destroyed after each call because otherwise the function will always output the same sequence - so they must be static or exist outside the scope of the function. On the other hand I need to be able to control the range/seed and so the variables need to be modifiable within the function.

Is this possible with the utilities provided by random_default_engine and uniform_int_distribution?

Silversonic
  • 1,289
  • 2
  • 11
  • 26
  • After researching a bit better it seems I might have to shy away from creating a new distribution every time. ([See](http://stackoverflow.com/questions/30103356/distributions-and-internal-state) and all the links inside provided). It seems fine for `uniform_int_distribution` but maybe it might not be so simple for other distributions. So I'll follow the suggested method in std::rand_replacement and use the `param_type` member to define the range to generate the value: [See](http://coliru.stacked-crooked.com/a/ee7609d723862ea2). – Silversonic Dec 15 '15 at 15:21
  • Alternatively if there is a wish to generate different distributions for different ranges: [See](http://coliru.stacked-crooked.com/a/6f93c70ddb7a3db2). Of course one could go further and use a `tuple` to make sure the same generators are used with the same distributions, I'm not entirely sure that's at all necessary. – Silversonic Dec 15 '15 at 15:26

1 Answers1

2

Keep the engine static but the distribution non-static, e.g.

unsigned randomUns(unsigned minV, unsigned maxV, default_random_engine::result_type seed = 0){
    static default_random_engine e(seed);
    uniform_int_distribution<unsigned> u(minV, maxV);
    return u(e);
}

To allow a custom seed being changed every call, store the seed too and if different then recreate the engine.

Maybe something like

unsigned randomUns(unsigned minV, unsigned maxV, default_random_engine::result_type seed = 0){
    static default_random_engine e(seed);
    static default_random_engine::result_type last_seed = seed;

    if (seed != last_seed){
        e = default_random_engine(seed);
        last_seed = seed;
    }

    uniform_int_distribution<unsigned> u(minV, maxV);
    return u(e);
}
Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • I was curious whether the distribution needs to be static. My source says "do it" but doesn't really explain why: _Similarly, distributions may retain state and should also be defined outside loops._ – Silversonic Dec 14 '15 at 13:29
  • @Silversonic because static initialization would only require it to initialize once. So it would be generally faster and cleaner then to re-initialize it everytime. However if you have a dynamic input it wouldn't be possible. So basically: If you can make something static (const), do it. – Neijwiert Dec 14 '15 at 13:30
  • @Silversonic Also declaring a static local variable in a function roughly translates to a check: if(!variableInitialized) { variable = initValue; } – Neijwiert Dec 14 '15 at 13:34
  • @Neijwiert OK. But I'm guessing this is only okay with `uniform_int_distribution`? [Some answers in this SO question](http://stackoverflow.com/questions/14857597/should-i-call-reset-on-my-c-std-random-distribution-to-clear-hidden-state) suggests resetting the state of other distributions types may ruin the output distribution. – Silversonic Dec 14 '15 at 13:57
  • @Silversonic In the case of `RNG`'s this holds to true, yes. Since it would re-initialize everytime you call the function it would return the same number for each seed given min max. You could work around this and still have static initialization by using templates. Or if the seeds are dynamically created, you'd need to use some kind of pool for `RNG`'s – Neijwiert Dec 14 '15 at 14:00