为什么 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_cast
从 std::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++。
如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_cast
从 std::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++。