28

So i have a Random object:

typedef unsigned int uint32;

class Random {
public:
    Random() = default;
    Random(std::mt19937::result_type seed) : eng(seed) {}

private:
    uint32 DrawNumber();
    std::mt19937 eng{std::random_device{}()};
    std::uniform_int_distribution<uint32> uniform_dist{0, UINT32_MAX};
};

uint32 Random::DrawNumber()
{
    return uniform_dist(eng);
}

What's the best way I can vary (through another function or otherwise) the upper bound of of the distribution?

(also willing to take advice on other style issues)

LordAro
  • 1,112
  • 3
  • 12
  • 31
  • You can't vary the bounds of the `distribution` after first initializing them. How you should proceed depends a lot on what you use your random numbers for and why you need to change the bounds. – us2012 Sep 26 '13 at 18:47
  • @us2012 That is wrong; you can. – underscore_d Sep 26 '18 at 11:15
  • Possible duplicate of [Use std::uniform\_int\_distribution and define its range later](https://stackoverflow.com/questions/28328948/use-stduniform-int-distribution-and-define-its-range-later) (pre-empting objections: yes, that's newer and has a somewhat tangential title, but IMO it has a more on-topic accepted answer and generally gets to the point faster) – underscore_d Sep 26 '18 at 11:15
  • 1
    At the risk of being biased, the 2 different questions have essentially the same accepted answer - "Another way would be to add a method..." part of the other question's answer is the same as the accepted answer here. This answer also includes usage, with a std::mt19937. That said the `.param` method is new. Since this question is now 5 years old, perhaps the best course of action would be to mark the other as a duplicate and amend the accepted answer here? Alternatively just leave the questions as is :) – LordAro Sep 26 '18 at 12:09

3 Answers3

47

Distribution objects are lightweight. Simply construct a new distribution when you need a random number. I use this approach in a game engine, and, after benchmarking, it's comparable to using good old rand().

Also, I've asked how to vary the range of distribution on GoingNative 2013 live stream, and Stephen T. Lavavej, a member of the standard committee, suggested to simply create new distributions, as it shouldn't be a performance issue.

Here's how I would write your code:

using uint32 = unsigned int;

class Random {
public:
    Random() = default;
    Random(std::mt19937::result_type seed) : eng(seed) {}
    uint32 DrawNumber(uint32 min, uint32 max);

private:        
    std::mt19937 eng{std::random_device{}()};
};

uint32 Random::DrawNumber(uint32 min, uint32 max)
{
    return std::uniform_int_distribution<uint32>{min, max}(eng);
}
Vittorio Romeo
  • 82,972
  • 25
  • 221
  • 369
  • I think i just watched that Going Native stream :) Although, i'm (a bit) new to C++ (especially C++11) and a fair amount of it went over my head :L If you're sure that there's no significant overhead in creating a new distribution each time, i'll probably go with this – LordAro Sep 26 '13 at 19:00
  • 2
    I've benchmarked it. Overhead is minimal and can be ignored. (This is being said by a micro-optimization nazi). Also, there is *no other way* of changing a distribution's bounds. – Vittorio Romeo Sep 26 '13 at 19:01
  • Ok, great, thanks very much :) I'll probably go with a constant 0 as the min, and UINT32_MAX as a default for max. – LordAro Sep 26 '13 at 19:03
  • 2
    This *creates* a new distribution, rather than modifying an existing one. As such, it doesn't answer the question, regardless of how quickly the class in instantiated. – Brett Hale Sep 07 '18 at 07:42
12

You can simply create a std::uniform_int_distribution<uint32>::param_type and modify the range using the param() method. You can cut down the template noise with decltype :

decltype(uniform_dist.param()) new_range (0, upper);
uniform_dist.param(new_range);
Brett Hale
  • 20,019
  • 2
  • 47
  • 81
7

I'm making the DrawNumber function public for my example. You can provide an overload that takes an upper bound, and then pass a new uniform_int_distribution::param_type to uniform_int_distribution::operator()

The param_type can be constructed using the same arguments as the corresponding distribution.

From N3337, §26.5.1.6/9 [rand.req.dist]

For each of the constructors of D taking arguments corresponding to parameters of the distribution, P shall have a corresponding constructor subject to the same requirements and taking arguments identical in number, type, and default values. Moreover, for each of the member functions of D that return values corresponding to parameters of the distribution, P shall have a corresponding member function with the identical name, type, and semantics.

where D is the type of a random number distribution function object and P is the type named by D's associated param_type

#include <iostream>
#include <random>

typedef unsigned int uint32;

class Random {
public:
    Random() = default;
    Random(std::mt19937::result_type seed) : eng(seed) {}

    uint32 DrawNumber();
    uint32 DrawNumber(uint32 ub);

private:
    std::mt19937 eng{std::random_device{}()};
    std::uniform_int_distribution<uint32> uniform_dist{0, UINT32_MAX};
};

uint32 Random::DrawNumber()
{
    return uniform_dist(eng);
}

uint32 Random::DrawNumber(uint32 ub)
{
    return uniform_dist(eng, decltype(uniform_dist)::param_type(0, ub));
}

int main()
{
  Random r;
  std::cout << r.DrawNumber() << std::endl;
  std::cout << r.DrawNumber(42) << std::endl;
}
Praetorian
  • 100,267
  • 15
  • 224
  • 307