Howard Hinnant - 10 days ago 6
C++ Question

What is the best way to form custom std::chrono::durations and std::ratios?

I was reading this excellent answer which used the comical time duration unit

`microfortnights`
to illustrate a good point in a memorable way.

``````typedef std::ratio<756, 625> microfortnights;
std::chrono::duration<int, microfortnights> two_weeks(1000000);
``````

And the question occurred to me:

If I really wanted to do this (more likely some other non-trivial
duration such as the time available during a frame, or during N cycles
of a processor), what is the best way to do this?

I know
`ratio<N, D>`
will create a unique type associated with each value of
`N`
and
`D`
. So
`ratio<4, 6>`
is a different type than
`ratio<2, 3>`
, even though they represent the same (reduced) fraction. Do I always have to do the math to simplify the conversion factor to reduced terms?

It would be more convenient to write:

``````using microfortnights = std::chrono::duration<long, ratio<86400*14, 1000000>>;
``````

``````using microfortnights = std::chrono::duration<long, ratio<756, 625>>;
``````

But then these would be two different types, instead of the same type. The first expression is easier to inspect for correctness. But there are many representations of this fraction, and the second is arguably canonical, and thus preferred. If I have too many types wandering around my program which actually represent the same unit, then that may lead to unnecessary template code bloat.

Below I'm ignoring namespaces in the interest of being concise. `duration` is in namespace `std::chrono`, and `ratio` is in namespace `std`.

There are two good ways to always ensure that your `ratio` is reduced to lowest terms without having to do the arithmetic yourself. The first is quite direct:

The direct formulation

If you just want to jump straight to `microfortnights`, but without having to figure out that the reduced fraction of 86,400*14/1,000,000 is 756/625, just add `::type` after the `ratio`:

``````using microfortnights = duration<long, ratio<86400*14, 1000000>::type>;
``````

The nested `type` of every `ratio<N, D>` is another `ratio<Nr, Dr>` where `Nr/Dr` is the reduced fraction `N/D`. If `N/D` is already reduced, then `ratio<N, D>::type` is the same type as `ratio<N, D>`. Indeed, had I already figured out that 756/625 was the correct reduced fraction, but was just paranoid in thinking it might could be reduced further, I could have written:

``````using microfortnights = duration<long, ratio<756, 625>::type>;
``````

So if you have any doubt that your `ratio` is expressed in lowest terms, or just don't want to be bothered with checking, you can always append the `::type` to your `ratio` type just to be sure.

The verbose formulation

Custom time duration units often pop up as part of a family. And it is often convenient to have the entire family available to your code. For example `microfortnights` is obviously related to `fortnights`, which in turn is related to `weeks`, which is derived from `days`, which is derived from `hours` (or from `seconds` if you prefer).

By building up your family one unit at a time, you not only make the entire family available, you also reduce the chance of errors by relating one family member to another with the simplest possible conversion. Additionally, making use of `std::ratio_multiply` and `std::ratio_divide`, instead of multiplying literals also means you don't have to keep insert `::type` everywhere to ensure that you keep your `ratio` in lowest terms.

For example:

``````using days = duration<long, ratio_multiply<hours::period, ratio<24>>>;
``````

`ratio_multiply` is a typedef-name to the result of the multiplication already reduced to lowest terms. So the above is the exact same type as:

``````using days = duration<long, ratio<86400>>;
``````

You can even have both definitions in the same translation unit, and you will not get a re-definition error. In any event you can now say:

``````using weeks           = duration<long, ratio_multiply<days::period,       ratio<7>>>;
using fortnights      = duration<long, ratio_multiply<weeks::period,      ratio<2>>>;
using microfortnights = duration<long, ratio_multiply<fortnights::period, micro>>;
``````

And we have ended up with a typedef-name for `microfortnights` that is the exact same type as in our direct formulation, but through a series of much simpler conversions. We still do not have to be bothered with reducing fractions to lowest terms, and we now have several useful units instead of just one.

Also note the use of `std::micro` in place of `std::ratio<1, 1000000>`. This is another place to avoid careless errors. It is so easy (at least for me) to mistype (and misread) the number of zeroes.