pack (type erase) a random number generator

2019-06-15 08:34发布

问题:

The C++11 std library has several random number generators (RNG), each implementing the concept UniformRandomNumberGenerator. These can then be used as argument for random distributions, see also this documentation for an overview.

The advantage of this design is that the choice of the underlying RNG engine is de-coupled from its application. However, the design also requires the definition (not merely the declarations) of all calls to the RNG to be available (if the RNG type is to remain unspecified as template parameter). Thus, in

struct complicated_random_distribution
{
  /*
     some data and auxiliary methods here
  */
  // complicated; may call RNG::operator() many times
  template<typename RNG>
  some_type operator()(RNG&gen) const;
};

the member operator() cannot be straightforwardly implemented in a separate compilation unit (CU), but must be available in the same header file (or one #included from it).

For a separate implementation, one ideally would want some way to pack a RNG in the same way as std::function<> packs any callable object. (Simply using std::function and providing the values for RNG::min() and RNG::max() as arguments to a function defined in a separate CU is restrictive and will not allow to use, say, std::uniform_real_distribution<> inside).

How can this be done? Are implementations for this available? Will the std library provide this in the future? Or am I after a red herring?


Edit Random number generators are required to have static members min() and max(), making type-erasure hard or impossible (GNU's libstdc++ doesn't make this assumption and a type erasure with non-static members min() and max() works, but not with LLVM's libc++, which uses the standard required static members). Is there a way to still solve this problem? If not, doesn't this imply that the C++ standard has a botched interface for random number generators?

回答1:

Adapt the RNG with an independent_bits_engine, and type erase the adapted RNG. You have full knowledge of what the independent_bits_engine's min() and max() are.

Here's a sketch:

struct RNG_wrapper {
    using result_type = std::uint32_t;
    static constexpr result_type min() { return 0; }
    static constexpr result_type max() { return 0xFFFFFFFF; }

    template<class RNG>
    using my_engine_type = std::independent_bits_engine<RNG, 32, result_type>;

    template<class RNG,
             class = std::enable_if_t<!std::is_same<std::decay_t<RNG>, RNG_wrapper>{}>>
    RNG_wrapper(RNG&& r)
        : rng(my_engine_type<std::decay_t<RNG>>(std::forward<RNG>(r))) {}

    result_type operator()() { return rng(); }
    std::function<result_type()> rng;
};


回答2:

This works in clang and gcc (with libstdc++), but this is not strictly a UniformRandomNumberGenerator, for the noted reason that min and max are not static.

#include <cassert>
#include <random>
#include <functional>
#include <iostream>

template<typename T>
class AnyUniformRandomNumberGenerator
{
    T mMin;
    T mMax;
    std::function<T()> mValue;
public:

    using result_type = T;

    template<typename UniformRandomNumberGenerator>
    AnyUniformRandomNumberGenerator(UniformRandomNumberGenerator uniformRandomNumberGenerator) :
    mMin(UniformRandomNumberGenerator::min()),
    mMax(UniformRandomNumberGenerator::max()),
    mValue([=]() mutable { return uniformRandomNumberGenerator(); })
    {}

    T operator()()
    {
        return mValue();
    }

    T min()
    {
        return mMin;
    }

    T max()
    {
        return mMax;
    }
};

int main()
{
    std::default_random_engine rng;
    AnyUniformRandomNumberGenerator<decltype(rng())> any{rng};

    assert(any() <= any.max());
    assert(any() >= any.min());

    std::uniform_int_distribution<> dist{1, 6};

    std::cout << dist(any);
}

GCC: http://rextester.com/ANAP79935

clang http://rextester.com/YCIIR21607



标签: c++ c++11 random