C++ Random Number Generation I am making a few wrapper classes for the C++ standard library and was wondering if the following code is good practice.

#pragma once

#include &lt;random&gt;

namespace LibraryName
{
    class RandomBase
    {
    protected:
        RandomBase()
            : m_RandomGenerator(m_RandomDevice()) {}

        std::random_device m_RandomDevice;
        std::mt19937 m_RandomGenerator;
    };

    template&lt;typename T&gt;
    class IntRandom : public RandomBase
    {
    public:
        IntRandom(T minValue, T maxValue)
            : m_Distribution(minValue, maxValue), m_MinValue(minValue), m_MaxValue(maxValue) {}

        T Next()
        {
            return m_Distribution(m_RandomGenerator);
        }

        inline T GetMinValue() const { return m_MinValue; }
        inline T GetMaxValue() const { return m_MaxValue; }

    private:
        std::uniform_int_distribution&lt;T&gt; m_Distribution;
        T m_MinValue;
        T m_MaxValue;
    };

    template&lt;typename T&gt;
    class FloatRandom : public RandomBase
    {
    public:
        FloatRandom(T minValue, T maxValue)
            : m_Distribution(minValue, maxValue), m_MinValue(minValue), m_MaxValue(maxValue) {}

        T Next()
        {
            return m_Distribution(m_RandomGenerator);
        }

        inline T GetMinValue() const { return m_MinValue; }
        inline T GetMaxValue() const { return m_MaxValue; }

    private:
        std::uniform_real_distribution&lt;T&gt; m_Distribution;
        T m_MinValue;
        T m_MaxValue;
    };

    class BoolRandom : public RandomBase
    {
    public:
        BoolRandom() : BoolRandom(0.5f) {}
        BoolRandom(float trueChance)
            : m_Distribution(trueChance), m_TrueChance(trueChance) {}

        bool Next()
        {
            return m_Distribution(m_RandomGenerator);
        }

        inline bool GetTrueChance() const { return m_TrueChance; }

    private:
        std::bernoulli_distribution m_Distribution;
        float m_TrueChance;
    };
}
That seems like a lot of code for very little purpose. It's allowing you to write

IntRandom&lt;int&gt; r(17, 42);
int i = r.Next();


instead of the no-helpers version,

std::mt19937 g(std::random_device{}());
std::uniform_int_distribution&lt;int&gt; dist(17, 42);
int i = dist(g);


Some people would say that that's not worth the 77 lines of code.



Two small problems with your code, related to your use of random_device: First, you're using only 32 bits of entropy to seed your entire Mersenne Twister; it would be better to use as many bits as the mt19937 has bits of state. However, doing this correctly is needlessly difficult in C++17, so you get a pass on it for now.

Second, your RandomBase class keeps a member of type random_device. This might be problematic, because a random_device is essentially an open file handle to /dev/urandom. If you create a lot of RandomBase objects at once, you might find yourself running out of file handles.

Oh, and a third problem is that random_device is non-copyable and non-movable; so your RandomBase is also non-movable. That could actually be pretty bad, depending on how you want to use it.



You could fix the random_device issues by removing the random_device data member, and instead providing a method like this:

void RandomBase::reseed() {
    std::random_device rd;
    m_RandomGenerator.seed(rd());  // TODO: better seeding
}


This keeps the random_device alive (and /dev/urandom open) only as long as it's needed; and it removes the data member from the class so that the class becomes movable and even copyable.



You could also reduce duplication by using templates and type-aliases, something like this:

template&lt;class Distribution&gt;
class dist_and_gen {
    std::mt19937 m_g;
    Distribution m_dist;

    template&lt;class... Args&gt;
    explicit constexpr dist_and_gen(Args&amp;&amp;...) :
        m_dist(std::forward&lt;Args&gt;(args)...) {}

    auto next() { return m_dist(m_g); }
    auto params() const { return m_dist.params(); }
};

template&lt;class T&gt; using IntRandom =
    dist_and_gen&lt;std::uniform_int_distribution&lt;T&gt;&gt;;
template&lt;class T&gt; using FloatRandom =
    dist_and_gen&lt;std::uniform_float_distribution&lt;T&gt;&gt;;
template&lt;class T&gt; using BoolRandom =
    dist_and_gen&lt;std::bernoulli_distribution&lt;T&gt;&gt;;


This version loses some of the details, such as your custom-named accessors for the parameters; but it preserves a lot of the details "accidentally". For example, I didn't try to preserve your BoolRandom::BoolRandom() default constructor; but it turns out that it just works anyway, thanks to the default constructor of the standard bernoulli_distribution.