Home > OS >  What's the use of <ratio> when we have contexpr values?
What's the use of <ratio> when we have contexpr values?

Time:04-02

The <ratio> header lets you uses template meta-programming to work with and manipulate rational values.

However - it was introduced in C 11, when we already had constexpr. Why is it not good enough to have a fully-constexpr'ifed library type for rationals, i.e. basically:

template<typename I>
struct rational { 
    I numerator;
    I denominator;
};

and use that instead?

Is there some concrete benefit to using std::ratio that C 11 constexpr functionality would not be well-suited enough for? And if so, is it still relevant in C 20 (with the expanded "reach" of constexpr)?

CodePudding user response:

Is there some concrete benefit to using std::ratio that C 11 constexpr functionality would not be well-suited enough for?

You can pass ratio as a template type argument, which is what std::chrono::duration does. To do that with a value-based ratio, you need C 20 or newer.

In C 20 and newer I don't see any benefits of the current design.

CodePudding user response:

There are several answers and comments here, but I think none of them really drives home the point of std::ratio. Let's begin with the definition of std::ratio. It is roughly equivalent to:

template<int Num, int Den>
struct ratio {
    static constexpr int num = Num;
    static constexpr int den = Den;
};

What you probably though, could be used as an alternative to std::ratio is something like the following:

template<typename I>
struct ratio {
    I num;
    I den;
};

with a bunch of constexpr functions to perform arithmetic with that type.

Note that there is a subtle but very important difference between the two definitions. Whereas in the second one the actual values of the ratio (num and den) are stored in the instances of the type, in the first definition the values are actually stored in the type itself.

If you have a library like std::chrono, you want for example a type, that can store a time as a number of milliseconds (e.g. std::chrono::milliseconds). If you later want to convert this number into seconds, you do not want to encode the conversion ratio into the instance of std::chrono::milliseconds but rather into the type itself. That is the reason why std::chrono uses the first form instead of the second (or a simple floating point value).

To store a number into a type and not the instance, you need a non-type template parameter. Before C 20 you only could use integral values for non-type template parameters. To nevertheless store rational conversion factors the standard library, specified the std::ratio class template.

With C 20 the tides changed a little bit, as you now can use floating point numbers as non-type template arguments. For example the std::chrono::duration could be rewritten like:

template<..., double conversion_factor, ...>
duration {
    ...
};

This C 20 feature has however nothing to do with "expanded reach of constexpr" that you mentioned in your question. Compilation time calculation is different from storing numerical values in the type itself, although you need the first to do the second.

The use of the std::ratio class template is made (exclusively) for storing values into the type.

CodePudding user response:

Why is it not good enough to have a fully-constexpr'ifed library type for rationals over type I, and use that instead?

One important point is, that any value below 1 can't be kept with integer values with constexpr alone. And if you need floating point numbers you run into accuracy and rounding troubles.

In addition, if you think on small embedded devices where the biggest int type is 16bit, you can have all values of in 32bits stored without any problem and math can be exact as needed while not running under the least significant bit of a floating point representation of that implementation.

  • Related