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... :)
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);
}
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);
}
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.