Search code examples
c++templatesoverloadinguniform-distribution

C++ - Templated Uniform Distribution?


Currently I am overloading this function to generate a random number:

float GetRand(float lower, float upper) {                                                                                                                                                      
    std::random_device rd;                                                                                                                                                                    
    std::mt19937_64 mt(rd());                                                                                                                                                                 
    std::uniform_real_distribution<float> dist(lower,upper);                                                                                                                                  
    return dist(mt);                                                                                                                                                                            
}                                                                                                                                                                                              

int GetRand(int lower, int upper) {                                                                                                                                                            
    std::random_device rd;                                                                                                                                                                    
    std::mt19937_64 mt(rd());                                                                                                                                                                 
    std::uniform_int_distribution<int> dist(lower,upper);                                                                                                                                     
    return dist(mt);                                                                                                                                                                          
}                                                                                                                                                                                             

Is it possible to do this with a template? I don't know how I could template the distribution.


Solution

  • We can unify both overloadings of GetRand as a function template.

    First of all, please note that the effect of std::uniform_real_distribution<T> is undefined if T is not one of float, double and long double. For instance, 29.6.1.1 General requirements [rand.req.genl] in C++ standard draft n4687 states:

    Throughout this subclause 29.6, the effect of instantiating a template:

    ...

    d) that has a template type parameter named RealType is undefined unless the corresponding template argument is cv-unqualified and is one of float, double, or long double.

    In addition, 29.6.8.2.2 Class template uniform_real_distribution [rand.dist.uni.real] describes std::uniform_real_distribution with the template type parameter RealType and therefore std::uniform_real_distribution<int> is undefined:

    template<class RealType = double>
    class uniform_real_distribution {
        ...
    };
    

    Also, similar restriction exists for std::uniform_int_distribution<T>. Thus we need to toggle the distribution type between std::uniform_real_distribution<T> and std::uniform_int_distribution<T> depending on T.


    We can check the above restrictions using std::is_floating_point and std::is_integral and make the following switch:

    #include <random>
    #include <type_traits>
    
    template<class T>
    using uniform_distribution = 
    typename std::conditional<
        std::is_floating_point<T>::value,
        std::uniform_real_distribution<T>,
        typename std::conditional<
            std::is_integral<T>::value,
            std::uniform_int_distribution<T>,
            void
        >::type
    >::type;
    

    Then two overloadings of GetRand can be unified to the following function template. Here I also avoid recursive construction of std::mt19937_64 and make the function thread-safe applying the accepted answer in this post.

    template <class T>
    T GetRand(T lower, T upper)
    {
        static thread_local std::mt19937_64 mt(std::random_device{}());
        uniform_distribution<T> dist(lower,upper);
    
        return dist(mt);
    }
    

    Finally, the caller side would be as follows:

    DEMO

    auto i = GetRand<int>   (0, 1); // 0 or 1
    auto f = GetRand<float> (0, 1); // [0, 1)
    auto d = GetRand<double>(0, 1); // [0, 1)