【问题标题】:std::sort - is passing a faulty comparator undefined behavior?std::sort - 是否传递了错误的比较器未定义行为?
【发布时间】:2016-04-22 09:47:25
【问题描述】:

考虑这段代码:

std::sort(vec.begin(), vec.end(),
    [](const Foo& lhs, const Foo& rhs) { return !(lhs < rhs); }
);

如果 lhs == rhs,则 lambda(lhs, rhs) 和 lambda(rhs, lhs) 都将返回 true,这违反了提供 严格 弱排序的要求。但是,标准是否明确将传递此类比较器标记为未定义行为?

【问题讨论】:

  • 好吧,如果行为被很好地定义了,那么诸如 Visual C++ 调试运行时之类的编译器将永远不会在运行时对这样的构造assert,而在非调试运行时,就让代码去吧把排序搞砸了。

标签: c++ stl undefined-behavior


【解决方案1】:

警告:接下来是极端的语言律师。

the most recent draft of the standard 的措辞在 [alg.sorting]p3 中是这样写的:

对于所有采用Compare 的算法,有一个版本改为使用operator&lt;。也就是说,comp(*i, *j) != false 默认为 *i &lt; *j != false。对于 25.4.3 中描述的算法以外的算法,comp 应该对值进行严格的弱排序。

使用“shall”这个词,标准是implicitly stating that violating it leads to undefined behavior

这是否要求给定函数对所有可能的值或仅对给定算法的值施加 SWO,标准中并不清楚。但是,由于在讨论这些特定算法的段落中说明了限制,因此假设它指的是提供给算法的值范围并不是不合理的。

否则,默认的operator&lt; 无法对floats 施加 SWO,这要感谢 NaN。

【讨论】:

  • Language-lawyerly,这里的“值”是指要排序的范围内的实际值,对吧?与在某些替代宇宙中可能存在于该范围内的独角兽相反。因此,如果我有办法知道范围内没有两个元素是等价的,那么我应该能够在没有 UB 的情况下使用该比较器。
  • @rici:详细信息已添加到答案中。
  • @rici 一个元素可以合法地与其自身进行比较,因此严格弱排序的“严格”部分开始起作用。所以除非你知道范围是空的(这会使问题变得相当无趣) ,违反非自反性的比较器还是不行。
  • @IgorTandetnik:公平点。我敢打赌它不会那样做,但我在标准中看不到任何保证。 (Nicol 对 NaN 的有趣观察仍然存在,不是吗?)
  • @rici 为了将一个不在范围内的元素传递给比较器,std::sort 需要构造这样一个元素 - 但它没有办法这样做(它只允许使用移动构造函数、移动赋值运算符和swap)。将比较器的行为要求放在永远不可能提供给它的输入上是毫无意义的。因此,虽然标准的文本不精确,但我认为将“对于所有x”解读为“对于出现在被排序范围内的所有x”是合理的。
【解决方案2】:

[alg.sorting]/3 使 25.4.3 中描述的算法以外的算法工作 正确地,comp 必须对值进行严格的弱排序。

[alg.sorting]/4 术语strict指的是非自反关系的要求(!comp(x, x) for all x)...

您的比较谓词不是严格的弱排序。

【讨论】:

  • 我想我们会留给读者一个 excersize 来决定一个不能“正常工作”的函数是否表现出未定义的行为。
  • @NicolBolas 嗯,有“[defns.undefined] 未定义的行为:本国际标准没有要求的行为”。不幸的是,对于std::sort,似乎没有什么比“无法正常工作”更强调的了。但除了“表现出未定义的行为”之外,很难以任何方式理解它。
  • 我知道;我通过文字很搞笑;)但从语言学究的角度来看,重要的是他们要清除 4-5 对“正确工作”的引用并将它们更改为更严格的内容。
  • 好吧,我认为“不能正常工作”意味着错误的输出,而不是崩溃。我并不是说这是正确的解释,而是标准可能应该使用更清晰、更严格的语言。
  • @user2478832:我将“正常工作”定义为“产生一个排列,以便比较算法将每个元素识别为大于或等于它之前的每个元素”,并且“工作不正确"作为产生任何其他排列。这将是“工作不正确”的最合乎逻辑的定义,可以将其与完全跳槽区分开来。
【解决方案3】:

这已经在Why will std::sort crash if the comparison function is not as operator <? 中被问及并得到了某种回答。

至少该线程中提出问题的人声称收到了异常

为了从标准中总结,我强调了与问题相关的部分。现在,“正常工作”的反义词可以解释为“未定义的行为”。

25.3 排序及相关操作[alg.sorting]

  1. 25.3 中的所有操作都有两个版本:一个采用比较类型的函数对象,另一个采用 运算符<.>
  2. Compare 用作函数对象,如果第一个参数小于第二个参数,则返回 true,否则返回 false 否则。比较 comp 始终用于假设排序关系的算法。假设 comp 不会通过取消引用的迭代器应用任何非常量函数。
  3. 对于所有采用比较的算法,有一个版本使用运算符中描述的算法以外的算法正常工作,comp 具有 对值进行严格的弱排序。
  4. 术语严格指的是对所有 x 的非自反关系 (!comp (x, x)) 的要求,而术语弱是指 要求不如完全排序的要求强,但比部分排序的要求强。要是我们 将 equiv(a, b) 定义为 !comp (a, b) && !comp (b, a),那么要求 comp 和 equiv 都是 传递关系:
    • comp (a, b) && comp (b, c) 隐含 comp (a, c)
    • equiv(a, b) && equiv(b, c) 隐含 equiv(a, c) [ 注意:在这些条件下,可以证明
      1. equiv 是等价关系
      2. comp 在由 equiv 确定的等价类上引入明确定义的关系
      3. 诱导关系是严格的全序。 ——尾注]

【讨论】:

  • 我所拥有的比例外更糟糕。 end 迭代器未被引用并被拖到容器中,这导致稍后崩溃。
  • 虽然您链接的问题密切相关,但并不完全相同。我不是在问为什么代码可能会崩溃,而是在问这样的代码是否被明确声明为具有未定义的行为。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-02
  • 1970-01-01
  • 2021-01-11
  • 1970-01-01
  • 2013-12-01
相关资源
最近更新 更多