drbombe
drbombe

Reputation: 639

C++11 Sharing the same function with different types of random distributions

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

Answers (4)

Edy
Edy

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

hongyu xiao
hongyu xiao

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

PR06GR4MM3R
PR06GR4MM3R

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

DanSnow
DanSnow

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

Related Questions