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

einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • 1
    Ratios and rational numbers are different things. Ratios are commonly used by the [chrono](https://en.cppreference.com/w/cpp/chrono) library for its [durations](https://en.cppreference.com/w/cpp/chrono/duration) – Some programmer dude Apr 01 '22 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. – NathanOliver Apr 01 '22 at 12:24
  • Additionally, `constexpr` doesn't always guarantee compile-time evaluation. Template metaprogramming, pretty much does. – Sam Varshavchik Apr 01 '22 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 Apr 01 '22 at 13:27
  • @NathanOliver It wasn't completely removed. – Spencer Jan 05 '23 at 16:52
  • @Spencer Specializing standard library functions was completely removed: https://stackoverflow.com/questions/52760580/will-specialization-of-function-templates-in-std-for-program-defined-types-no-lo/52760860#52760860 – NathanOliver Jan 05 '23 at 17:26
  • @NathanOliver We might just be dancing around definitions here? Member functions of `std::numeric_limits` specializations could be construed as "specialzations of standard library functions". – Spencer Jan 05 '23 at 17:29
  • @Spencer The standard only allows `a program may add a template specialization for any standard library class template` so that works as long as the limits specialization is for a custom type. – NathanOliver Jan 05 '23 at 17:31
  • @NathanOliver Yes, yes, I get that...it's just that the wording is vague and this is compounded by the fact that the Standard only prohibits them by omission. There should be a [Note] explicitly mentioning `std::swap` since that was the most frequently specialized function. – Spencer Jan 05 '23 at 17:39
  • @NathanOliver I used to joke about the (decidedly non-kosher) specialzation of swap for unsigned integers with the old 3-XOR version. – Spencer Jan 05 '23 at 17:40

3 Answers3

7

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.

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • 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). – Konrad Rudolph Apr 01 '22 at 12:52
  • 2
    @KonradRudolph I don't think OP proposes `double`? I think they suggest `template struct ratio {T num, den;};`. – HolyBlackCat Apr 01 '22 at 12:54
  • Oh I see, I think you’re right. – Konrad Rudolph Apr 01 '22 at 13:01
  • @einpoklum Done. :) – HolyBlackCat Apr 01 '22 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 Apr 01 '22 at 14:25
  • @Barry: I'd say that values are significantly easier to deal with than types... at least as of C++20. Boost.Mp11 sounds like a C++11 library, while the answers point to C++20 as the point at which the current design is not beneficial. – einpoklum Apr 01 '22 at 22:26
  • Another comment is that for @HolyBlackCat is that we could, instead of passing the struct - pass in the two integers as two template parameters rather than as a compound type. That would work n C++11 but would be perhaps "uglier" than using `std::ratio`. – einpoklum Apr 01 '22 at 22:54
6

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.

Jakob Stark
  • 3,346
  • 6
  • 22
  • "you do not want to encode the conversion ratio into the instance of std::chrono::milliseconds" I wouldn't do that, obviously. – einpoklum Apr 01 '22 at 13:41
  • @einpoklum I do not see how you would avoid that without using a `std::ratio`-like construct before C++20. – Jakob Stark Apr 01 '22 at 13:44
  • [You would still use `ratio`, not `double`](https://godbolt.org/z/xsM4fovTK). – Barry Apr 01 '22 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.

Red.Wave
  • 2,790
  • 11
  • 17