【发布时间】:2019-08-04 19:09:39
【问题描述】:
这个问题很清楚。下面给出了我认为这些表达式可能会产生未定义行为的原因。我想知道我的推理是对是错以及为什么。
短读:
(IEEE 754) double 不是 Cpp17LessThanComparable,因为 < 不是严格的弱排序关系,因为 NaN。因此,违反了std::min<double> 和std::max<double> 的Requires 元素。
长读:
所有引用都遵循n4800。 std::min 和 std::max 的规格在 24.7.8 中给出:
template<class T> constexpr const T& min(const T& a, const T& b);template<class T> constexpr const T& max(const T& a, const T& b);
要求:[...] 类型 T 应为 Cpp17LessThanComparable(表 24)。
表 24 定义了 Cpp17LessThanComparable 并说:
要求:
<是严格的弱排序关系 (24.7)
第 24.7/4 节定义了严格的弱排序。特别是,对于<,它声明“如果我们将equiv(a, b) 定义为!(a < b) && !(b < a),那么equiv(a, b) && equiv(b, c) 意味着equiv(a, c)”。
现在,根据 IEEE 754 equiv(0.0, NaN) == true、equiv(NaN, 1.0) == true 但equiv(0.0, 1.0) == false,我们得出结论,< 不是严格的弱排序。因此,(IEEE 754)doublenotCpp17LessThanComparable 违反了std::min 和std::max 的Requires 子句。
最后,15.5.4.11/1 说:
违反函数的 Requires: 元素中指定的任何先决条件会导致未定义的行为 [...]。
更新 1:
问题的重点不是争辩 std::min(0.0, 1.0) 是未定义的,当程序评估此表达式时,任何事情都可能发生。它返回0.0。时期。 (我从来没有怀疑过。)
重点是显示标准的(可能的)缺陷。在对精确度的值得称赞的追求中,该标准经常使用数学术语,弱严格排序只是一个例子。在这些情况下,数学精度和推理必须一路走好。
例如,看看维基百科对strict weak ordering 的定义。它包含四个要点,每个要点都以“For every x [...] in S...”开头。他们都没有说“对于 S 中对算法有意义的某些值 x”(什么算法?)。此外,std::min 的规范明确表示“T 应为Cpp17LessThanComparable”,这意味着< 是T 上的严格弱排序。因此,T 在 Wikipedia 页面中扮演集合 S 的角色,当考虑 T 的值时,四个要点必须保持完整。
显然,NaN 与其他双精度值完全不同,但它们仍然是可能的值。我在标准中看不到任何东西(这是相当大的,1719 页,因此这个问题和语言律师标签)数学上得出的结论是std::min 可以提供双打不涉及 NaN。
实际上,可以说 NaN 很好,而其他双打才是问题所在!事实上,回想一下,有几个可能的 NaN 双精度值(其中 2^52 - 1 个,每个都携带不同的有效负载)。考虑包含所有这些值和一个“正常”双精度值的集合 S,例如 42.0。在符号中,S = { 42.0, NaN_1, ..., NaN_n }。事实证明,< 是 S 上的严格弱排序(证明留给读者)。 C++ 委员会在指定std::min 时是否考虑了这组值,如“请不要使用任何其他值,否则严格的弱排序被破坏并且std::min 的行为未定义”?我敢打赌不是,但我更愿意在标准中阅读此内容,而不是推测“某些值”的含义。
更新 2:
对比std::min(上)与clamp 24.7.9的声明:
template<class T> constexpr const T& clamp(const T& v, const T& lo, const T& hi);
要求:lo的值不能大于hi。对于第一种形式,键入 T 应为 Cpp17LessThanComparable(表 24)。 [...]
[注意:如果避免使用NaN,则 T 可以是浮点类型。 ——尾注]
在这里,我们清楚地看到“std::clamp 可以在不涉及 NaN 的情况下使用双精度数”。我正在为std::min 寻找相同类型的句子。
值得注意的是,Barry 在其post 中提到的段落 [structure.requirements]/8。显然,这是在来自 P0898R0 的 C++17 之后添加的):
本文档中定义的任何概念所需的操作不必是全部功能;也就是说,所需操作的某些参数可能会导致无法满足所需的语义。 [示例:StrictTotallyOrdered 概念 (17.5.4) 所需的
<运算符在对 NaN 进行操作时不满足该概念的语义要求。 — 结束示例 ] 这不会影响类型是否满足概念。
这是解决我在这里提出的问题的明确尝试,但在概念的背景下(正如 Barry 所指出的,Cpp17LessThanComparable 不是一个概念)。此外,恕我直言,这一段也缺乏精确性。
【问题讨论】:
-
当行为未定义时,是因为可能的运行时值。一些函数/语言特性有一个狭窄的合同(例如,不能取消引用
nullptr)。而在这些情况下,程序员有责任排除这些情况。由于 UB 不能在constexpr上下文中发生,我尝试将std::min放入带有1.0/0一个参数的static_assert中,但它没有编译,因为我无法在编译时生成 NaN。我认为如果可以在编译时检测到违反要求,它应该只是使编译失败。无论如何,措辞是不幸的。 -
“重复”并没有说明有问题的代码是否是UB
-
发现以下论文讨论了该主题及其对排序等事物的影响:Comparison in C++
-
不幸的是,这个问题对 IEEE 浮点数的关注似乎适得其反,因为它与实际问题并不真正相关,但却吸收了很多词。可以很容易地做到something like this(这显然不是严格的弱排序,不需要谈论 NaN 或引用其他标准来确定这一点)。
标签: c++ floating-point language-lawyer undefined-behavior c++-standard-library