Being a quick sketch combining <chrono>
and <random>
functionality, with cryptarithmetic interludes…
At CppCon this year there were several good talks about randomness and time calculations in C++. On randomness: Walter Brown’s What C++ Programmers Need to Know About Header <random> and Cheinan Marks’ I Just Wanted a Random Integer! were both excellent talks. And Howard Hinnant gave several great talks: A <chrono> Tutorial, and Welcome to the Time Zone, a followup to his talk from last year, A C++ Approach to Dates and Times.
CHRONO + RANDOM = HORRID ?
That’s perhaps a little unfair, but recently I ran into the need to compute a random period of time. I think this is a common use case for things like backoff schemes for network retransmission. And it seemed to me that the interaction of <chrono>
and <random>
was not quite as good as it could be:
system_clock::duration minTime = 0s; system_clock::duration maxTime = 5s; uniform_int_distribution<> d(minTime.count(), maxTime.count()); // 'gen' here is a Mersenne twister engine auto nextTransmissionWindow = system_clock::duration(d(gen)); |
This code gets more complex when you start computing an exponential backoff. Relatively straightforward, but clumsy, especially if you want a floating-point base for your exponent calculation: system_clock::duration
has an integral representation, so in all likelihood you end up having to cast multiple times, using either static_cast
or duration_cast
. That’s a bit messy.
I remembered some code from another talk: Andy Bond’s AAAARGH!? Adopting Almost Always Auto Reinforces Good Habits!? in which he presented a function to make a uniform distribution by inferring its argument type, useful in generic code. Something like the following:
template <typename A, typename B = A, typename C = std::common_type_t<A, B>, typename D = std::uniform_int_distribution<C>> inline auto make_uniform_distribution(const A& a, const B& b = std::numeric_limits<B>::max()) -> std::enable_if_t<std::is_integral<C>::value, D> { return D(a, b); } |
Of course, the standard also provides uniform_real_distribution
, so we can provide another template and overload the function for real numbers:
template <typename A, typename B = A, typename C = std::common_type_t<A, B>, typename D = std::uniform_real_distribution<C>> inline auto make_uniform_distribution(const A& a, const B& b = B{1}) -> std::enable_if_t<std::is_floating_point<C>::value, D> { return D(a, b); } |
And with these two in hand, it’s easy to write a uniform_duration_distribution
that uses the correct distribution for its underlying representation (using a home-made type trait to constrain it to duration
types).
template <typename T> struct is_duration : std::false_type {}; template <typename Rep, typename Period> struct is_duration<std::chrono::duration<Rep, Period>> : std::true_type {}; template <typename Duration = std::chrono::system_clock::duration, typename = std::enable_if_t<is_duration<Duration>::value>> class uniform_duration_distribution { public: using result_type = Duration; explicit uniform_duration_distribution( const Duration& a = Duration::zero(), const Duration& b = Duration::max()) : m_a(a), m_b(b) {} void reset() {} template <typename Generator> result_type operator()(Generator& g) { auto d = make_uniform_distribution(m_a.count(), m_b.count()); return result_type(d(g)); } result_type a() const { return m_a; } result_type b() const { return m_b; } result_type min() const { return m_a; } result_type max() const { return m_b; } private: result_type m_a; result_type m_b; }; |
Having written this, we can once again overload make_uniform_distribution
to provide for duration
types:
template <typename A, typename B = A, typename C = std::common_type_t<A, B>, typename D = uniform_duration_distribution<C>> inline auto make_uniform_distribution(const A& a, const B& b = B::max()) -> D { return D(a, b); } |
And now we can compute a random duration
more expressively and tersely, and, I think, in the spirit of the existing functionality that exists in <chrono>
for manipulating duration
s.
auto d = make_uniform_distribution(0s, 5000ms); auto nextTransmissionWindow = d(gen); |
CHRONO + RANDOM = DREAMY
I leave it as an exercise for the reader to solve these cryptarithmetic puzzles. As for the casting problems, for now, I’m living with them.