【问题标题】:Why acts std::chrono::duration::operator*= not like built-in *=?为什么 std::chrono::duration::operator*= 不像内置 *=?
【发布时间】:2019-07-16 16:54:42
【问题描述】:

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。在以下示例中,在乘法发生之前,两个操作数都被提升为 double。 4.5的中间结果之后再转成int,这样结果就是4。

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

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

【问题讨论】:

  • rep 是 an arithmetic type representing the number of ticks,分钟是 /*signed integer type of at least 29 bits*/,而不是 float
  • POD 在这里不是正确的术语。您的意思可能是“内置”。
  • 除了术语,我 100% 同意 template &lt;class T&gt; duration&amp; operator*=(const T&amp; rhs); 版本会更直观。例如int i = 10; i *= 1.5; 也导致i == 15,我想知道为什么这里没有遵循“自定义类型应该像内置类型一样可用”的 C++ 教条。

标签: c++ c++11 duration


【解决方案1】:

这里的问题是

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

【讨论】:

    【解决方案2】:

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

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

    作为一个具体的例子,考虑m被初始化为11min的情况,并假设我们有一个模板化的operator*=,正如你所建议的那样。 确切答案现在是16.5min,但基于整数的类型chrono::minutes 无法表示此值。

    一个优秀的设计是有这样一行:

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

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


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

    这项工作将涉及:

    • 带有单元测试的实现。
    • 对其进行实地考察以了解它会破坏多少代码,并确保它不会破坏非预期的代码。
    • 写一篇论文提交给 C++ 委员会,目标是 C++23(目标是 C++20 已经太迟了)。

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

    【讨论】:

    • “基于积分的算术是精确的(或需要 duration_cast)还是无法编译。” (更多)与我上面提供的内置示例一致,哪个符合,哪个不准确?编译错误不会提供另一种不一致吗? (我会考虑写一个提案。我是这个过程的新手,所以可能需要一些时间来熟悉这个过程。:-))
    • 我在回答中将“一致”改为“自洽”。 IE。此更改将使库与自身更加一致。在其他算术表达式中,积分和浮点混合表达式的结果会产生基于浮点的结果。在 chrono 库中,基于浮点的类型不会在没有显式命名转换的情况下转换为基于整数的类型(例如 duration_casttime_point_cast,在 C++17 中,floorceil 和 @987654333 @)。
    • @MichaelReinhardt 10min * 1 正好是 10 分钟。精度损失发生在调用运算符之前以满足签名。这是,由于历史原因/从 C 继承),并且超出了 chrono 的控制范围。是的,正如 H.H. 所说,它可以被修复(或变通),例如通过添加 = deleted 的重载,但 IMO C 和 C++ 应该最终弃用潜在的有损隐式转换,以删除它们。是的,Linus Torvalds 将会对语言委员会有多糟糕进行了另一场史诗般的咆哮,但最终社区应该会变得更好。
    【解决方案3】:

    operator*=的实现:

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

    操作员接受const _Rep&amp;。它来自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&amp; _Right) 1.5f 时,int 等于 1,因此不会影响其自身的任何乘法运算。

    那你能做什么?

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

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

    10 分钟的 150%:15 分钟


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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-07-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-03
      • 1970-01-01
      • 2013-07-27
      相关资源
      最近更新 更多