Reputation: 639
I have two functions. The only difference of these two functions is that they are using different random number generators. For your convenient, I wrote some compilable codes as below. The two functions are
fillRandomVecInt
and
fillRandomVecNorm
How can we combine these two functions as one? Can I use a pointer pointing to the base class and dynamically hook up rnorm
and runifInt
? or use template to resolve this issue? Many thanks for your help.
#include <iostream>
#include<random>
using namespace std;
class RandomVec{
private:
unsigned seed = 0;
std::default_random_engine generator{seed};
std::normal_distribution<> rnorm{0,1};
std::uniform_int_distribution<> runifInt{0,1};
public:
void fillRandomVecInt(vector<int> & v);
void fillRandomVecNorm(vector<double> & v);
};
void RandomVec::fillRandomVecNorm(vector<double> &v) {
for(auto i=v.begin();i!=v.end();++i){
*i=rnorm(generator);
}
}
void RandomVec::fillRandomVecInt(vector<int> &v) {
for(auto i=v.begin();i!=v.end();++i){
*i=runifInt(generator);
}
}
int main() {
RandomVec rv;
vector<double> x=vector<double>(10);
rv.fillRandomVecNorm(x);
vector<int> y = vector<int>(10);
rv.fillRandomVecInt(y);
return 0;
}
Upvotes: 2
Views: 117
Reputation: 470
You can template a base class whose specialization provides a different distribution object according to its typename parameter.
Then, for every type that you want to support, only a small amount of code that specializes the class template with the corresponding distribution object is needed.
One advantage of this approach is that one specialized base class can support multiple typename T
. For example, in the example below, all integral (not just int
) and floating-point (not just double
) types are supported.
See the code below:
#include <iostream>
#include<random>
using namespace std;
namespace {
// Helper type defined since C++14.
template<bool B, class T = void>
using enable_if_t = typename std::enable_if<B,T>::type;
}
// Base class to be specialized with different distribution object.
template<typename T, typename Enable = void>
class RandomVecBase;
// Supports integers with uniform_int_distribution
template<typename T>
class RandomVecBase<T, enable_if_t<std::is_integral<T>::value> > {
public:
RandomVecBase() : distrib_{0, 1} {}
protected:
uniform_int_distribution<> distrib_;
};
// Supports floating-point with normal_distribution.
template<typename T>
class RandomVecBase<T, enable_if_t<std::is_floating_point<T>::value> > {
public:
RandomVecBase() : distrib_{0, 1} {}
protected:
normal_distribution<> distrib_;
};
// Actual class, code in fillRandomVec() only needs to be written once
template<typename T>
class RandomVec : public RandomVecBase<T> {
private:
unsigned seed = 0;
default_random_engine generator{seed};
public:
void fillRandomVec(vector<T>& v) {
for (auto& i : v) {
i = this->distrib_(generator);
}
}
};
int main() {
RandomVec<double> rv_double;
vector<double> x = vector<double>(10);
rv_double.fillRandomVec(x);
RandomVec<int> rv_int;
vector<int> y = vector<int>(10);
rv_int.fillRandomVec(y);
return 0;
}
Upvotes: 0
Reputation: 239
#include<time.h>
#include<algorithm>
#include<functional>
#include <iostream>
#include<random>
using namespace std;
namespace stackoverflow
{
//just for this topic
template<typename _FwdIt,typename _RngFn>
void random_fill(_FwdIt first, _FwdIt last, _RngFn&& fn)
{ //random fill the range [first,last) by means of fn
_DEBUG_RANGE(first, last);
generate(first, last, fn);
}
}
//random function-fn is provided by yourself, for example
int main() {
using namespace stackoverflow;
static default_random_engine e(time(0));
std::normal_distribution<> rnorm{ 0,1 };
std::uniform_int_distribution<> runifInt{ 0,1 };
std::uniform_int_distribution<> runifInt_1{ 2,10 };
std::uniform_real_distribution<> runifDouble{ 0,1 };
vector<int> x(10);
//type int can be random fill throw normal or uniform distribution or other, and
//some distribution parameter can change
random_fill(x.begin(), x.end(), bind(ref(runifInt),ref(e)));
random_fill(x.begin(), x.end(), bind(ref(runifInt_1), ref(e)));
random_fill(x.begin(), x.end(), bind(ref(rnorm), ref(e)));
vector<double> y(10);
//type double can be random fill throw normal or uniform distribution or other, and
//some distribution parameter can change
random_fill(y.begin(), y.end(), bind(ref(rnorm),ref(e)));
random_fill(y.begin(), y.end(), bind(ref(runifDouble), ref(e)));
return 0;
}
I think STL algorithm is the most helpful, So I design these mutating STL. For the object defined in the main function you can move them into the stackoverflow namespace as you like, but don't them global.
Upvotes: 0
Reputation: 397
Maybe I am misunderstanding what you are asking, but couldn’t you just use function overloading?
void fillRandomVec(vector<int>& v) {
//code...
}
void fillRandomVec(vector<double>& v {
//code...
}
Or...
//this is a function I have used that I copied in
template<class T>
bool vectIsInt(std::vector<T>& v)
{
for (int i = 0; i < v.size(); i++)
{
if (v[i] != floor(v[i]) return false;
}
return true;
}
template<class U>
void fillRandomVec(std::vector<U>& v)
{
if (vectIsInt(v))
{
//code if int vect
}
else
{
//code if double vect...
}
}
Upvotes: 1
Reputation: 121
I'll suggest to use template specialization.
class RandomVec {
private:
unsigned seed = 0;
std::default_random_engine generator{seed};
std::normal_distribution<> rnorm{0, 1};
std::uniform_int_distribution<> runifInt{0, 1};
template<typename T>
T generateRandom(void);
public:
template<typename T>
void fillRandomVec(vector<T> &v);
};
template<>
int RandomVec::generateRandom(void) {
return runifInt(generator);
}
template<>
double RandomVec::generateRandom(void) {
return rnorm(generator);
}
template<typename T>
void RandomVec::fillRandomVec(vector<T> &v) {
for (auto i = v.begin(); i != v.end(); ++i) {
*i = generateRandom<T>();
}
}
Upvotes: 3