为什么行为 std::chrono::d uration::operator*= 不像内置的 *=?



如 std::chrono::d uration::operator+= 中所述,签名是

duration& operator*=(const rep& rhs);

这让我很纳闷。我假设持续时间文字可以像任何其他内置文本一样使用,但它没有。

#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;
}

输出为

150% of 10min: 10min
150% of 10: 15

为什么选择这种界面?在我看来,像这样的界面

template<typename T> 
duration& operator*=(const T& rhs);

会产生更直观的结果。

编辑:
感谢您的回复,我知道实现的行为方式以及我如何处理它。我的问题是,为什么它是这样设计的。

我希望在操作结束时转换为 int。在下面的示例中,两个操作数在乘法发生之前都提升为双精度。之后,4.5 的中间结果将转换为 int,因此结果为 4。

int i = 3;
i *= 1.5;
assert(i == 4);

我对std::duration的期望是它的行为方式相同。

这里的问题是

auto m = 10min;

为您提供一个std::chrono::duration其中rep是有符号整数类型。 当你这样做时

m *= 1.5f;

1.5f被转换为类型rep这意味着它被截断为1,这在乘法后给你相同的值。

要解决此问题,您需要使用

auto m = 10.0min;

获取对rep使用浮点类型并且在执行m *= 1.5f;时不会截断1.5fstd::chrono::duration

我的问题是,为什么它是这样设计的。

它是这样设计的(具有讽刺意味的是),因为基于积分的计算旨在给出精确的结果,或者不编译。 但是,在这种情况下,<chrono>库无法控制在绑定到参数之前将哪些转换应用于参数。

作为一个具体的例子,考虑m初始化为11min的情况,并假设我们有一个模板化operator*=如你所建议的。确切的答案现在16.5min,但基于积分的类型chrono::minutes不能表示这个值。

一个更好的设计是有这样一条线:

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

不编译。 这将使库更加自洽:基于积分的算术要么是精确的(或者需要duration_cast),要么不能编译。 这是可以实现的,而为什么没有这样做的答案只是我没有想到它。


如果您(或其他任何人)对此有足够强烈的感觉,试图标准化上述声明的编译时错误,我愿意在委员会中发言支持这样的提案。

这项工作将涉及:

  • 具有单元测试的实现。
  • 对它进行跟踪以了解它将破坏多少代码,并确保它不会破坏非预期的代码。
  • 写一篇论文并提交给C++委员会,目标C++23(现在瞄准C++20为时已晚)。

最简单的方法是从开源实现开始,例如gcc的libstdc++或llvm的libc ++。

查看operator*=的实现:

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

操作员采取const _Rep&.它来自std::duration,看起来像:

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

所以现在如果我们看一下std::chrono::minutes的定义:

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

很明显,_Rep是一个int.


因此,当您调用operator*=(const _Rep& _Right)1.5f时,它正在投射到int- 这等于1,因此不会影响与自身的任何多重关系。

那你能做什么呢?

您可以将其拆分为m = m * 1.5f,并使用std::chrono::duration_caststd::chrono::duration<float, std::ratio>转换为std::chrono::duration<int, std::ratio>

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

150%的10分钟:15分钟


如果您不喜欢总是强制转换它,请使用它的第一个模板参数作为float

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

甚至更快 -auto m = 10.0min; m *= 1.5f;正如@NathanOliver回答的那样:-)

最新更新