Why acts std::chrono::duration::operator*= not like built-in *=?

The issue here is

auto m = 10min;

gives you a std::chrono::duration where rep is a signed integer type. When you do

m *= 1.5f;

the 1.5f is converted to the type rep and that means it is truncated to 1, which gives you the same value after multiplication.

To fix this you need to use

auto m = 10.0min;

to get a std::chrono::duration that uses a floating point type for rep and wont truncate 1.5f when you do m *= 1.5f;.


My question is, why is it designed that way.

It was designed this way (ironically) because the integral-based computations are designed to give exact results, or not compile. However in this case the <chrono> library exerts no control over what conversions get applied to arguments prior to binding to the arguments.

As a concrete example, consider the case where m is initialized to 11min, and presume that we had a templated operator*= as you suggest. The exact answer is now 16.5min, but the integral-based type chrono::minutes is not capable of representing this value.

A superior design would be to have this line:

m *= 1.5f;  // compile-time error

not compile. That would make the library more self-consistent: Integral-based arithmetic is either exact (or requires duration_cast) or does not compile. This would be possible to implement, and the answer as to why this was not done is simply that I didn't think of it.


If you (or anyone else) feels strongly enough about this to try to standardize a compile-time error for the above statement, I would be willing to speak in favor of such a proposal in committee.

This effort would involve:

  • An implementation with unit tests.
  • Fielding it to get a feel for how much code it would break, and ensuring that it does not break code not intended.
  • Write a paper and submit it to the C++ committee, targeting C++23 (it is too late to target C++20).

The easiest way to do this would be to start with an open-source implementation such as gcc's libstdc++ or llvm's libc++.


Looking at the implementation of operator*=:

_CONSTEXPR17 duration& operator*=(const _Rep& _Right)
    {   // multiply rep by _Right
    _MyRep *= _Right;
    return (*this);
    }

the operator takes a const _Rep&. It comes from std::duration which looks like:

template<class _Rep, //<-
    class _Period>
    class duration
    {   // represents a time Duration
    //...

So now if we look at the definition of std::chrono::minutes:

using minutes = duration<int, ratio<60>>;

It is clear that _Rep is an int.


So when you call operator*=(const _Rep& _Right) 1.5f is beeing cast to an int - which equals 1 and therefore won't affect any mulitiplications with itself.

So what can you do?

you can split it up into m = m * 1.5f and use std::chrono::duration_cast to cast from std::chrono::duration<float, std::ratio> to std::chrono::duration<int, std::ratio>

m = std::chrono::duration_cast<std::chrono::minutes>(m * 1.5f);

150% of 10min: 15min


if you don't like always casting it, use a float for it as the first template argument:

std::chrono::duration<float, std::ratio<60>> m = 10min;
m *= 1.5f; //> 15min

or even quicker - auto m = 10.0min; m *= 1.5f; as @NathanOliver answered :-)