1
votes

I want to create a template random number generator class which can either be an integer type or floating point type. Why? For an assignment I wrote an accumulate function (essentially the same as std::accumulate) and I want to make a test harness which can be any integer or floating point type (e.g., unsigned|short|long|long long int, float, double). We've been studying templates and I'm trying to get my arms around using template programming to make dynamic compile-time decisions. I may be approaching this the wrong way - any suggestions/references greatly appreciated.

Here's my test function:

void testdrive() {
    std::vector<int> vint(ELEMENTS);
    std::vector<double> vflt(ELEMENTS);
    RNG<int> intrng;
    RNG<double> fltrng;

    std::generate(vint.begin(), vint.end(), intrng)
    std::generate(vflt.begin(), vflt.end(), fltrng)

    std::cout << "Sum of " << printvec(vint) << "is " accum(vint) << "\n\n";
    std::cout << "Sum of " << printvec(vflt) << "is " accum(vflt) << '\n';
}

I can't figure out how to do this with template programming for my class. What I want to do is if the type is an int type use uniform_int_distribution and if it's float|double use uniform_real_distribution. I realize the two aren't exactly interchangeable but for what I'm trying to do it's fine. Here's my class:

template<typename T>
class RNG {
    public:
        RNG(T low=std::numeric_limits<T>::min(),
            T high=std::numeric_limits<T>::max())
            : rng_engine{rng_seed()}, rng_dist{low, high}, rng_low{low},
              rng_high{high} { }
        RNG(const RNG& r): rng_engine{rng_seed()}, rng_dist{r.rng_low,
            r.rng_high}, rng_low{r.rng_low}, rng_high{r.rng_high} { }
        T operator()() { return rng_dist(rng_engine); }
    private:
        std::random_device rng_seed;
        std::mt19937 rng_engine;
        template<typename U, typename=std::enable_if_t<std::is_integral<T>::value>>
            std::uniform_int_distribution<T> rng_dist;
        template<typename U, typename=std::enable_if_t<std::is_floating_point<T>::value>>
            std::uniform_real_distribution<T> rng_dist;
        T rng_low, rng_high;
};

Also, for anyone reading this post I've found this book extremely helpful for diving into C++ Templates: C++ Templates - The Complete Guide 2nd Edition (http://www.tmplbook.com/)

2
Why do you want to do this? Why do you want to bundle the generator with a distribution, as well as tying the distribution to a type that it has no association with? Especially considering that uniform_int and uniform_real don't have the same behavior (the latter provides a half-open range, while the former is a closed range). - Nicol Bolas
@NicolBolas Thanks for looking. I revised my question and hopefully that answers yours. Please let me know if not. - James S.

2 Answers

2
votes

Take a look at template specialization. In the code below, the private struct Distribution selects which std::uniform_*_distribution to use.

#include <stdio.h>
#include <vector>
#include <algorithm>
#include <random>
#include <iostream>

template <class T>
class RNG
{
    // primary template is designed for integers
    template <class U>
    struct Distribution
    {
        typedef std::uniform_int_distribution<U> Type;
    };
    // explicit specialization for float
    template <>
    struct Distribution<float>
    {
        typedef std::uniform_real_distribution<float> Type;
    };
    // explicit specialization for double
    template <>
    struct Distribution<double>
    {
        typedef std::uniform_real_distribution<double> Type;
    };

    std::random_device rng_source;
    typename Distribution<T>::Type rng_dist;

public:
    RNG(
        T low = std::numeric_limits<T>::min(),
        T high = std::numeric_limits<T>::max())
        : rng_source{}
        , rng_dist(low, high)
    {
    }
    RNG(const RNG& rhs)
        : rng_source{}
        , rng_dist(rhs.rng_dist)
    {
    }

    T operator()()
    {
        return rng_dist(rng_source);
    }
};

int main()
{
    const size_t ELEMENTS = 10;
    std::vector<int> vint(ELEMENTS);
    std::vector<double> vflt(ELEMENTS);
    RNG<int> intrng(0, 100);
    RNG<double> fltrng(0.0, 1.0);

    std::generate(vint.begin(), vint.end(), intrng);
    std::generate(vflt.begin(), vflt.end(), fltrng);

    return 0;  <-- set a breakpoint here to see both vectors
}
1
votes

Here's what I came up with, but I like @fifoforlifo's answer much better:

template<typename T>                                                      
class RNG {                                                               
    static_assert(std::is_arithmetic<T>::value,                           
                  "Only primitive numeric types supported.");             
    public:                                                               
        RNG(T low=std::numeric_limits<T>::min(),                          
            T high=std::numeric_limits<T>::max())                         
            : rng_engine{rng_seed()}, rng_dist{low, high}, rng_low{low},  
              rng_high{high} { }                                          
        RNG(const RNG& r): rng_engine{rng_seed()}, rng_dist{r.rng_low,    
            r.rng_high}, rng_low{r.rng_low}, rng_high{r.rng_high} { }     
        T max() { return rng_dist.max(); }                                
        T min() { return rng_dist.min(); }                                
        T operator()() { return rng_dist(rng_engine); }                   
    private:                                                              
        std::random_device rng_seed;                                      
        std::mt19937 rng_engine;                                          
        std::uniform_int_distribution<T> rng_dist;                        
        T rng_low, rng_high;                                              
};                                                                        

// Specialize RNG                                                         
// Really want a generic way to support any floating point type           
// e.g., float, double, long double                                       
// And ideally this would all be in one template class...                 
template<>                                                                
class RNG<double> {                                                       
    public:                                                               
        RNG(double low=std::numeric_limits<double>::min(),                
            double high=std::numeric_limits<double>::max())               
            : rng_engine{rng_seed()}, rng_dist{low, high}, rng_low{low},  
              rng_high{high} { }                                          
        RNG(const RNG& r): rng_engine{rng_seed()}, rng_dist{r.rng_low,    
            r.rng_high}, rng_low{r.rng_low}, rng_high{r.rng_high} { }     
        double max() { return rng_dist.max(); }                           
        double min() { return rng_dist.min(); }                           
        double operator()() { return rng_dist(rng_engine); }              
    private:                                                              
        std::random_device rng_seed;                                      
        std::mt19937 rng_engine;                                          
                std::uniform_real_distribution<double> rng_dist;          
        double rng_low, rng_high;                                         
};