As described in std::chrono::duration::operator+= the signature is
duration& operator*=(const rep& rhs);
This makes me wonder. I would assume that a duration literal can be used like any other built-in, but it doesn't.
#include <chrono>
#include <iostream>
int main()
{
using namespace std::chrono_literals;
auto m = 10min;
m *= 1.5f;
std::cout << " 150% of 10min: " << m.count() << "min" << std::endl;
int i = 10;
i *= 1.5f;
std::cout << " 150% of 10: " << i << std::endl;
}
Output is
150% of 10min: 10min
150% of 10: 15
Why was the interface choosen that way? To my mind, an interface like
template<typename T>
duration& operator*=(const T& rhs);
would yield more intuitive results.
EDIT:
Thanks for your responses, I know that the implementation behaves that way and how I could handle it. My question is, why is it designed that way.
I would expect the conversion to int take place at the end of the operation. In the following example both operands get promoted to double before the multiplications happens. The intermediate result of 4.5 is converted to int afterwards, so that the result is 4.
int i = 3;
i *= 1.5;
assert(i == 4);
My expectation for std::duration
would be that it behaves the same way.
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:
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 :-)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With