Search code examples
c++constructorprivate-class

Is this construct a proper use of a private class, and how do I put its instances in that map?


Trying to find a somewhat clean design for sampling billions of dice rolls and while my first ugly solution did work by just creating the generators for the known ranges (dice) it was an ugly mess of multiple typed out by hand copies only changing the range, and switching which to answer the common roll() method with.

#include <random>
#include <map>


namespace dnd {

    class Dice {
    private:
        
        class RandomNumberGenerator {
        public:
            RandomNumberGenerator(int from, int to) {
                distr = std::uniform_int_distribution(from, to);
            }

        private:
            std::random_device rand_dev;
            std::mt19937 generator = std::mt19937();
            std::uniform_int_distribution<int> distr;
        public:
            int roll() {
                return distr(generator);
            }
        };

        static std::map<int, RandomNumberGenerator> generators;

    public:

        static int dN(int n) {
            if (!generators.contains(n)) {
                generators[n] = RandomNumberGenerator(1, n);
            }
            return generators[n].roll();
        }
    };

MSVS error

Is this construct a proper use of a private class, and how do I put its instances in that map? I suppose something is already wrong with the constructor, despite MSVS not complaining? Not sure if even the basic idea is sound (in C++).

I am trying to move from only creating some generators and create them at program start to create every one I need, as using

    public:
        template<typename T>
        static T random(T range_from, T range_to) {
            std::random_device                  rand_dev;
            std::mt19937                        generator(rand_dev());
            std::uniform_int_distribution<T>    distr(range_from, range_to);
            return distr(generator);
        }

        static int d4() {
            static std::random_device                  rand_dev;
            static std::mt19937                        generator(rand_dev());
            static std::uniform_int_distribution<int>    distr(1, 4);
            return distr(generator);
        }
``` ... d20.

is way to slow for the dice not prepared and ugly code wise preparing the expected by hand.

Solution

  • std::random_device can't be copied, therefore instances of your class can't be copied. Bright side, there doesn't seem to be any reason to have a std::random_device member, since you don't use it, and even if you did (to seed the mt19937), you don't need to keep it after it's been used. Just change:

    std::random_device rand_dev;
    std::mt19937 generator = std::mt19937();  // Should have been std::mt19937(rand_dev()) to actually seed it at all
    

    to:

    // Seeds it with a temporary instance of random_device
    std::mt19937 generator = std::mt19937(std::random_device{}());
    

    to both create and initialize the generator without preserving a std::random_device that never gets used again (it serves no purpose after that point).

    You also need to avoid the use of generators[n] (which implicitly requires a default constructor for RandomNumberGenerator, which it does not have), and make some other small cleanups (e.g. using initialization of distr instead of reassigning it after default initialization, and I had to move the definition of generators inside dN's definition or you get linker errors for reasons I'm not 100% clear on), but once that is done, it works just fine:

    #include <random>
    #include <map>
    #include <iostream>
    
    namespace dnd {
    
        class Dice {
        private:
            
            class RandomNumberGenerator {
            public:
                RandomNumberGenerator(int from, int to) : distr(from, to) {}
    
            private:
                std::mt19937 generator = std::mt19937(std::random_device{}());
                std::uniform_int_distribution<int> distr;
            public:
                int roll() {
                    return distr(generator);
                }
            };
    
        public:
    
            static int dN(int n) {
                static std::map<int, RandomNumberGenerator> generators;
                if (!generators.contains(n)) {
                    generators.emplace(n, RandomNumberGenerator(1, n));
                }
                return generators.find(n)->second.roll();
            }
        };
    }
    
    int main() {
       for (int i = 0; i < 10; ++i) {
          std::cout << dnd::Dice::dN(12) << std::endl;
       }
    }
    

    Attempt This Online!