Uniform random distribution “base class” for both

2019-07-03 16:42发布

I'm trying to make a function that will fill a list with random numbers, and based on the type of the list items it should generate either integer or floating point numbers. So far I've come up with the following code, and it works:

template <typename T>
void generateRandom(list<T>& numberList){
    default_random_engine randomGenerator(random_device{}());

    if( typeid(T) == typeid(int) ){
        uniform_int_distribution<T> distribution(1000, 2000);
        auto myGenerator = bind(distribution, randomGenerator);
        generate(numberList.begin(), numberList.end(), myGenerator);
    }
    else if( typeid(T) == typeid(double) ){
        uniform_real_distribution<T> distribution(1000.0, 2000.0);
        auto myGenerator = bind(distribution, randomGenerator);
        generate(numberList.begin(), numberList.end(), myGenerator);
    }
    else{
        return;
    }

}

However, I'm not really satisfied with this solution. It just feels like there should be some way to break out everything but the actual distribution from that IF statement. Something like:

template <typename T>
void generateRandom(list<T>& numberList){
    default_random_engine randomGenerator(random_device{}());

    X distribution;
    if( typeid(T) == typeid(int) )
        distribution = uniform_int_distribution<T>(1000, 2000);
    else if( typeid(T) == typeid(double) )
        distribution = uniform_real_distribution<T>(1000.0, 2000.0);
    else
        return;

    auto myGenerator = bind(distribution, randomGenerator);
    generate(numberList.begin(), numberList.end(), myGenerator);
}

(X is a placeholder for what's missing here)

But since uniform_int_distribution and uniform_real_distribution don't have a common base class, how would that be possible? I have tried playing around with function pointers and functors, but I never seem to get past that IF-statement, and it's annoying. Can this be solved in a more elegant way?

NOTE: It's part of a school assignment, and we have to use generate() and the <random> functions and templates to generate the random numbers. I think my original solution is acceptable, I just don't like the look of those duplicate lines of code in the IF-statement... :)

3条回答
走好不送
2楼-- · 2019-07-03 16:57

You can create a typetrait-like class:

template<class T>
struct Distribution {};

template<>
struct Distribution<int> {
    typedef uniform_int_distribution<int> type;
};

template<>
struct Distribution<double> {
    typedef uniform_real_distribution<double> type;
};

template <typename T>
void generateRandom(list<T>& numberList){
    default_random_engine randomGenerator(random_device{}());

    typename Distribution<T>::type distribution = typename Distribution<T>::type(1000, 2000);
    ...

Another option is to create a template helper function that will accept the needed distribution as argument:

template<class T, template<class> class Distr>
void generateRandomHelper(list<T>& numberList){
    default_random_engine randomGenerator(random_device{}());

    Distr<T> distribution = Distr<T>(1000, 2000);

    auto myGenerator = bind(distribution, randomGenerator);
    generate(numberList.begin(), numberList.end(), myGenerator);
}

template<class T>
void generateRandom2(list<T>& numberList);

template<>
void generateRandom2<int>(list<int>& numberList) {
    generateRandomHelper<int, uniform_int_distribution>(numberList);
}

template<>
void generateRandom2<double>(list<double>& numberList) {
    generateRandomHelper<double, uniform_real_distribution>(numberList);
}
查看更多
时光不老,我们不散
3楼-- · 2019-07-03 16:57

You are trying to calculate a type. That calls for (very, very, very) simple template meta programming:

template<typename T> class Distribution;
template<> class Distribution<int> {
    using distribution = uniform_int_distribution<int>;
};
template<> class Distribution<double> {
    using distribution = uniform_real_distribution<int>;
};


template <typename T>
void generateRandom(list<T>& numberList){
    default_random_engine randomGenerator(random_device{}());

    Distribution<T>::distribution distribution(1000, 2000);

    auto myGenerator = bind(distribution, randomGenerator);
    generate(numberList.begin(), numberList.end(), myGenerator);
}

This code does not compile, when T is neither int nor double. The compiler will complain, that there is no type Distribution<T>::distribution for your T.

Assuming, silently doing nothing e.g. for float, or unsigned int, is really what you need:

template<typename T> class Distribution {
public:
    // just anything, so that there is a type
    // not sure if this is really necessary
    using distribution = uniform_int_distribution<int>; 
    static const bool valid = false;
};
template<> class Distribution<int> {
public:
    using distribution = uniform_int_distribution<int>;
    static const bool valid = true;
};
template<> class Distribution<double> {
public:
    using distribution = uniform_real_distribution<double>;
    static const bool valid = true;
};


template <typename T>
void generateRandom(list<T>& numberList){
    default_random_engine randomGenerator(random_device{}());

    if(!Distribution<T>::valid) return;

    Distribution<T>::distribution distribution(1000, 2000);
    auto myGenerator = bind(distribution, randomGenerator);
    generate(numberList.begin(), numberList.end(), myGenerator);
}

Now the default just silently does nothing. To actually generate random numbers other than int or double, you have to specialize the template for your type, like I did for int and double.

查看更多
smile是对你的礼貌
4楼-- · 2019-07-03 17:08

You may use:

template <typename T>
using my_distribution = std::conditional_t<std::is_integral<T>::value,
                   std::uniform_int_distribution<T>,
                   std::conditional_t<std::is_floating_point<T>::value,
                                      std::uniform_real_distribution<T>,
                                      void>
                   >;

And then

template <typename T>
void generateRandom(list<T>& numberList){
    default_random_engine randomGenerator(random_device{}());
    my_distribution<T> distribution(1000, 2000);
    auto myGenerator = bind(distribution, randomGenerator);
    generate(numberList.begin(), numberList.end(), myGenerator);
}
查看更多
登录 后发表回答