Agrim Pathak
Agrim Pathak

Reputation: 3207

std::uniform_real_distribution regenerating same random number

In running the code below, the first half of arr equals the last half. Why? I even tried various seeds, e.g. std::chrono::system_clock::now().time_since_epoch().count(). Thanks.

#include <algorithm>
#include <iostream>
#include <random>


template<typename DistributionType>
class Rng 
{
public:
        template<typename ...Args>
        Rng(Args&&... args) : dist(args...) { } 

        typename DistributionType::result_type operator()()
        {
                return dist(gen);
        }

private:
        std::default_random_engine gen;

        DistributionType dist;
};


class UniformRealRng : public Rng<std::uniform_real_distribution<double>>
{
public:
        UniformRealRng(const double a, const double b) : Rng(a, b) { } 
};

int main()
{
        constexpr int sz = 6;
        constexpr int k  = sz / 2;
        double arr[sz];

        UniformRealRng rng(0.0, 1.0);
        std::generate(arr, arr + k, rng);
        std::generate(arr + k, arr + sz, rng);

        for (int i = 0; i < sz; ++i)
        {
                std::cout << arr[i];
        }
        std::cout << "\n";
}

Upvotes: 2

Views: 117

Answers (2)

alfC
alfC

Reputation: 16242

Your answer is factually correct but it doesn't solve the problem. It only produces a compilation error when mistakenly(?) one tries to copy the random number generator.

It turns out that the code can be made to be semantically correct by using the std::reference_wrapper library features.

    std::generate(arr, arr + k, std::ref(rng));
    std::generate(arr + k, arr + sz, std::ref(rng));

In this way you are basically forcing to pass the argument by reference. Luckily the reference wrapper overloads the operator() and therefore can be used for the generator without any additional code.

Complete code:

#include <algorithm>
#include <iostream>
#include <random>
#include <functional> //ref

template<typename DistributionType>
class Rng 
{
public:
        template<typename ...Args>
        Rng(Args&&... args) : dist(args...) { } 
//      Rng(Rng&)             = delete;  // this is not needed for it to work 
//      Rng& operator=(Rng&)  = delete;  // you MAY want to copy the generator
        typename DistributionType::result_type operator()()
        {
                return dist(gen);
        }

private:
        std::default_random_engine gen;

        DistributionType dist;
};


class UniformRealRng : public Rng<std::uniform_real_distribution<double>>
{
public:
        UniformRealRng(const double a, const double b) : Rng(a, b) { } 
};

int main()
{
        constexpr int sz = 6;
        constexpr int k  = sz / 2;
        double arr[sz];

        UniformRealRng rng(0.0, 1.0);
        std::generate(arr, arr + k, std::ref(rng));
        std::generate(arr + k, arr + sz, std::ref(rng));

        for (int i = 0; i < sz; ++i)
        {
                std::cout << arr[i];
        }
        std::cout << "\n";
}

Upvotes: 0

Agrim Pathak
Agrim Pathak

Reputation: 3207

std::generate takes its third argument by value, so rng was being copied.

For safety, one could delete copying:

Rng(Rng&)             = delete;   
Rng& operator=(Rng&)  = delete;

Upvotes: 3

Related Questions