9

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

10
  • 1
    Ratios and rational numbers are different things. Ratios are commonly used by the chrono library for its durations Commented Apr 1, 2022 at 12:23
  • Not sure if it was part of the decision, but specializing library functions for custom types was removed from C++, where specializing types is still there. Commented Apr 1, 2022 at 12:24
  • Additionally, constexpr doesn't always guarantee compile-time evaluation. Template metaprogramming, pretty much does. Commented Apr 1, 2022 at 12:25
  • @SamVarshavchik: That's true, but then you could wrap the library type with your own TMP "value" which does guarantee compile-time evaluation.
    – einpoklum
    Commented Apr 1, 2022 at 13:27
  • @NathanOliver It wasn't completely removed.
    – Spencer
    Commented Jan 5, 2023 at 16:52

3 Answers 3

9

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.

7
  • One advantage is that std::ratio gives you exact representations of arbitrary fractions across the entire domain (within the data type range), whereas double doesn’t. How often that’s actually needed is another question (and it’s probably not neeed for std::chrono::duration, in particular). Commented Apr 1, 2022 at 12:52
  • 2
    @KonradRudolph I don't think OP proposes double? I think they suggest template <typename T> struct ratio {T num, den;};. Commented Apr 1, 2022 at 12:54
  • Oh I see, I think you’re right. Commented Apr 1, 2022 at 13:01
  • @einpoklum Done. :) Commented Apr 1, 2022 at 13:40
  • 2
    One benefit of the current design: types are significantly easier to deal with than values for metaprogramming purposes. For instance: std::chrono::duration works with Boost.Mp11 if ratio is a type parameter, but not if it's a value parameter.
    – Barry
    Commented Apr 1, 2022 at 14:25
7

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.

3
  • "you do not want to encode the conversion ratio into the instance of std::chrono::milliseconds" I wouldn't do that, obviously.
    – einpoklum
    Commented Apr 1, 2022 at 13:41
  • 1
    @einpoklum I do not see how you would avoid that without using a std::ratio-like construct before C++20. Commented Apr 1, 2022 at 13:44
  • You would still use ratio, not double.
    – Barry
    Commented Apr 1, 2022 at 16:27
0

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.

2
  • Add links please...
    – einpoklum
    Commented Apr 4, 2022 at 9:23
  • @einpoklum just click on boost everywhere.
    – Red.Wave
    Commented Apr 4, 2022 at 15:45

Not the answer you're looking for? Browse other questions tagged or ask your own question.