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

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)?



Solution 1:[1]

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.

Solution 2:[2]

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.

Solution 3:[3]

boost::rational was provided about the same time as boost::ratio. However since the later was used by boost::chrono which was in turn used by boost::thread, it was easier to add the whole bunch together to the standard. If one seeks a full-fledged run-time usable rational number class, boost::rational is there. The boost::multiprecision is also available to provide very long integral types as the argument for boost::rational. Regarding the standardization of other parts of boost such as boost::random, it has been a big question to me why boost::rational is not yet added to to standard library.

Regards, FM.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3