为什么 std::chrono::duration::operator*= 不像内置的 *=?

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

std::chrono::duration::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;

获得一个 std::chrono::duration,它使用 rep 的浮点类型,并且当您执行 m *= 1.5f;.

时不会截断 1.5f

查看 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% of 10min: 15min


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

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

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

My question is, why is it designed that way.

它是这样设计的(具有讽刺意味的是)因为基于积分的计算旨在给出准确的结果,或者不编译。然而,在这种情况下,<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++。