为什么 std::chrono::weekday 允许但不保留有效范围之外的值?

Why does `std::chrono::weekday` allow but doesn't preserve values outside of valid range?

根据 C++ 标准的 [time.cal.wd.overview]/1 部分:

weekday represents a day of the week in the civil calendar. It normally holds values in the range 0 to 6, corresponding to Sunday through Saturday, but it may hold non-negative values outside this range.

同时算术运算执行模7算术,强制结果在[0, 6]范围内,例如

weekday wd(7);
// wd.ok() == false - wd is invalid
++wd; // wd == weekday(1)
// wd.ok() == true  - wd silently becomes valid

为什么 weekday 有这种奇怪的行为,特别是为什么 [0, 6] 之外的值允许但不被算术运算保留?

没那么奇怪

如果你给它一个超出范围的值,那是你的错误。强迫我们所有人忍受无休止的边界检查只是为了捕捉这种罕见的情况是不公平的。

同样,期望这种类型的明确定义的操作对明确认为超出范围的值做任何事情是不公平的,也不清楚您期望从这种类型中得到什么样的确定性结果一个手术。 "ooglebooglebargleday" 之后是哪一天?

您会在整个 C++ 中发现很多这种哲学。你不用为不用的东西付钱,只要你不介意自己背着回家的路,你就可以搬起石头砸自己的脚。

Why does weekday have such peculiar behavior, particularly why are values outside of [0, 6] allowed but not preserved by arithmetic operations?

weekday 上的任何算术运算都可能溢出。所以你必须在那里做模运算。 sat + days{1} == sun 很重要,因为这是每个人都期望的。你真的希望 (sat + days{1}).ok() 也成立,因为这肯定是有效的。 sat + days{8} == sunsat + days{701} == sun 等也同样重要。这就是日历算法的工作原理。

因此,对所有算术运算进行模 7 是非常有必要的。将 8 作为工作日值毫无意义 - 这不是真正有效的工作日。

另一方面,对构造进行取模并没有那么明确的价值。这是您可能不需要做的额外工作,它甚至可能隐藏错误。如果你只想验证 weekday(user_input).ok() 怎么办?每个人都需要知道要从外部检查什么吗?

简而言之,6 以上的值在构造中是允许的,因为允许是有意义的,但算术不会保留它们,因为保留它们没有意义。

但是霍华德经常为 SO 撰稿,所以他可能会插话

weekday(unsigned wd) 构造函数承诺保存 [0, 255] 范围内的任何值。这样做的理由是:

  1. 速度非常快。
  2. 它允许客户端将 "unused" 值分配给在客户端逻辑中有用的东西。

以(2)为例:

constexpr weekday not_a_weekday{255};
...
weekday wd = not_a_weekday;
in >> wd;
if (wd == not_a_weekday)
    throw "oops";

weekday 算术强制范围回到 [0, 6] 因为如果你编写算法来做模 7 算术,根本没有范围检查,这就是自然发生的事情。 IE。这是最快的事情。


所以总而言之:性能是当前 weekday 规范的基本原理,结合尽可能小的 sizeof(这也有助于提高性能)。

然而,尽可能多地提供性能,无论留下什么行为(自然发生)都可以有利于标准化并让客户利用这些行为,而不是说它们是未定义的行为TM .

事实上,规范尽可能地避免了 UB,而是选择了未指定的行为。例如weekday{300}可能不会存储你想要的值,但它不能重新格式化你的磁盘,并且不允许优化器假装代码不存在。