Previous question:
Generate a random numbers class
I have come up with three different ways to define type of mUniformDistribution
. All of them work fine in VC++ 2013 and GCC.
First approach by SFINAE:
template<typename U >
static typename std::enable_if<
std::is_integral<U>::value,
std::uniform_int_distribution<U>
>::type dist();
template<typename U >
static typename std::enable_if<
std::is_floating_point<U>::value,
std::uniform_real_distribution<U>
>::type dist();
decltype(dist<T>()) mUniformDistribution;
Second approach by SFINAE:
template<typename U, typename std::enable_if<std::is_integral<U>::value>::type* = nullptr>
static auto dist() -> std::uniform_int_distribution<U>;
template<typename U, typename std::enable_if<std::is_floating_point<U>::value>::type* = nullptr>
static auto dist() -> std::uniform_rea_distribution<U>;
decltype(dist<T>()) mUniformDistribution;
Third approach:
I'm quite comfortable with it for simplicity.
template <typename T>
using dist = typename std::conditional<
std::is_integral<T>::value,
std::uniform_int_distribution<T>,
std::uniform_real_distribution<T>>::type;
dist<T> mUniformDistribution;
which one is the better approach and why?
Code sample:
#include <random>
#include <iostream>
#include <type_traits>
template<typename T >
class Random
{
public:
Random(const T& min, const T& max)
: mUniformDistribution(min, max)
{}
T operator()()
{
return mUniformDistribution(mEngine);
}
private:
std::default_random_engine mEngine{ std::random_device()() };
/////////////////////////////////////////
// first approach
//
//template<typename U >
//static typename std::enable_if<
// std::is_integral<U>::value,
// std::uniform_int_distribution<U>
//>::type dist();
//
//template<typename U >
//static typename std::enable_if<
// std::is_floating_point<U>::value,
// std::uniform_real_distribution<U>
//>::type dist();
//decltype(dist<T>()) mUniformDistribution;
//////////////////////////////////////////
// second approach
//
//template<typename U, typename std::enable_if<std::is_integral<U>::value>::type* = nullptr>
//static auto dist() -> std::uniform_int_distribution<U>;
//
//template<typename U, typename std::enable_if<std::is_floating_point<U>::value>::type* = nullptr>
//static auto dist() -> std::uniform_real_distribution<U>;
//decltype(dist<T>()) mUniformDistribution;
//////////////////////////////////////////
// third approach
//
template <typename U>
using dist = typename std::conditional<
std::is_integral<U>::value,
std::uniform_int_distribution<U>,
std::uniform_real_distribution<U>>::type;
dist<T> mUniformDistribution;
};
int main()
{
Random<int> getRandom(0, 9);
for (int i = 0; i<9; ++i)
std::cout << getRandom() << '\n';
}
1 Answer 1
Each uniform distribution type has 3 valid constructors.
uniform_[int|real]_distribution() : uniform_real_distribution(0.0) { }
explicit uniform_[int|real]_distribution( RealType a, RealType b = 1.0 );
explicit uniform_[int|real]_distribution( const param_type& params );
Rather than accept only the 2nd constructor, take the arguments as a pack and forward them to the distributions constructors.
Random(const T& min, const T& max) : mUniformDistribution(min, max) {}
dist<T> mUniformDistribution;
T operator()()
{
return mUniformDistribution(mEngine);
}
Clients are locked into only uniform int or real distributions. Distributions are cheap to construct, so needing to store them isn't necessary.
std::default_random_engine mEngine{ std::random_device()() };
Clients are locked into std::default_random_engine
, which is implementation-defined.
- libstdc++ -
std::minstd_rand0
by Park and Miller (1988) - libcpp -
std::minstd_rand
by Park, Miller, and Stockmeyer (1993) - MS STL -
std::mt19937
by Matsumoto and Nishimura (1988).
Clients are locked into std::random_device
for the seed. std::random_device
has the following problems:
- Doesn't conform to the requirements of seed sequence. The generated value from the device may not be large enough to seed the random engine correctly.
- The entropy sources are implemention-defined. The cost to read from the device is unspecified.
- The values generated may be deterministic.
There is no access to the underlying random engine to either advance the state, serialize/deserialize the internal state, or reset the engine.
There is no way to change the bounds of the distribution without constructing a new instance of Random
which could be costly to initialize new random engines.
template <typename U>
using dist = typename std::conditional<
std::is_integral<U>::value,
std::uniform_int_distribution<U>,
std::uniform_real_distribution<U>>::type;
I'd go with this as you are just making a compile time choice and both types are well-formed. Even better, extract this from the class and give it an appropriate name.
template <typename T>
using uniform_distribution = typename std::conditional_t<
std::is_integral_v<T>,
std::uniform_int_distribution<T>,
std::uniform_real_distribution<T>>;
Melissa O'Neill wrote a wrapper for random engines (blog post | gist - randutils.hpp). You can read more <random>
seeding issues on her blog.