【问题标题】:Should set::equal_range() return pair( set::lower_bound(), set::upper_bound() )应该 set::equal_range() 返回对( set::lower_bound(), set::upper_bound() )
【发布时间】:2021-04-11 08:09:10
【问题描述】:

是的,我知道,我应该有一个复制品,但复制品制作起来很烦人。在我制作一个(并且可能给 clang 一个错误)之前,我想确保我对事物应该如何工作的理解是正确的。

  • MSVC:将set::equal_range() 作为{lower_bound(),upper_bound()} 返回。

  • Clang:将set::equal_range() 作为{lower_bound(),upper_bound()-1} 返回。 (使用 stdlib=libc++)——当然是我的例子。

我对过去 20 年 STL 编程的期望是 MSVC 是正确的。事实上,我正在编辑的代码是我 20 年前写的,它曾经在 clang 和 gcc 中工作,但现在只能在 MSVC 中工作(也许 gcc - 我还没有尝试过 - 我正在使用一些标题C++20 且仅在 stdlib=libc++ 和 MSVC 中,但不在 gcc 中)。

无论如何,我对 set::equal_range() 应该返回什么的假设是否正确?

好吧,看看吧:

Clang 与 -stdlib=libc++:https://godbolt.org/z/xfnasKa73

没有 -stdlib=libc++ 的 Clang:https://godbolt.org/z/5qYEj4MEd

如果您认为我的比较器是假的,请告诉我。

这里是测试代码:

#include <iostream>
#include <utility>
#include <set>

using namespace std;

struct Range : public pair< size_t, size_t >
{
    using pair::pair;
    bool operator < ( Range const & _r ) const
    {
        return second < _r.first;
    }
};

typedef set< Range > vTySet;

int
main()
{
    vTySet setRanges = {{0,0},{9,9},{10,10},{13,13},{32,32},{34,34},{61,61},{65,90},{101,101},{110,110}};
    Range rngTest = { 97, 122 };
    pair< vTySet::iterator, vTySet::iterator > pritEqualRange = setRanges.equal_range( rngTest );
    size_t nDistEqualRange = distance( pritEqualRange.first, pritEqualRange.second );
    pair< vTySet::iterator, vTySet::iterator > pritLowerUpper = { setRanges.lower_bound( rngTest ), setRanges.upper_bound( rngTest ) };
    size_t nDistLowerUpper = distance( pritLowerUpper.first, pritLowerUpper.second );
    cout << "nDistEqualRange:" << nDistEqualRange << "\n";
    cout << "nDistLowerUpper:" << nDistLowerUpper << "\n";
}

【问题讨论】:

  • 来自en.cppreference.com/w/cpp/container/set/equal_range“第一个迭代器可以用lower_bound()获得,第二个用upper_bound()。”所以,也许minimal reproducible example会有所帮助澄清为什么你会得到这些值。
  • 我无法想象 Clang 和 MS 会为 set::equal_range() 返回不同的东西。很可能您的代码中有一些 UB,如果该 UB 的后果,您会看到差异。
  • 如果您有自定义比较器,请检查它是否满足strict weak ordering requirements
  • 你的理解是正确的。如果 clang 为第二个迭代器返回 upper_bound()-1,那么您的代码有错误或 libc++ 有错误。您通常不会在标准库实现中发现错误,因此您真的需要创建一个重现问题的minimal reproducible example
  • @DavidBien 它有时会发出警告的事实并不等同于保证它始终能够检测到它。

标签: c++ language-lawyer c++-standard-library


【解决方案1】:

用于std::set 的任何自定义比较器都应满足Compare 要求(严格的弱排序+ 等价)。你的代码中有 UB,你看到的不同是那个 UB 的结果。在您的示例中,元素是对 (a, b),比较器(我们称之为 less)是 (a, b) &lt; (c, d) iff b &lt; c

考虑两对(5, 2)(6, 3)。不对称属性要求如果(5, 2) &lt; (6, 3) 为真,那么(6, 3) &lt; (5, 2) 应为假。但这也是真的:

Range r1(5, 2);
Range r2(6, 3);
std::cout << less(r1, r2); // 1
std::cout << less(r2, r1); // 1, but should be 0

等价关系的传递性也被这样的比较器打破了:

Range r1(1, 3);
Range r2(2, 5);
Range r3(4, 6);
std::cout << equiv(r1, r2); // 1
std::cout << equiv(r2, r3); // 1
std::cout << equiv(r1, r3); // 0, but should be 1

Demo

应该注意,这些要求不仅适用于集合中的元素,还适用于您作为键传递给std::set::equal_range(const Key&amp;) 的元素。

【讨论】:

  • 是的,但是 if 我保证所有对都是不相交的,那么它将是一个很好的比较器 - 即它是一个很好的比较器用于不相交的集合。然后我可以将 (lower_bound(), upper_bound()) 与集合成员不相交的搜索参数一起使用 - 即就像我正在做的那样。在这种情况下,只有 equal_range() 会返回一个虚假的结果 - 并且由于实现细节 - 即它适用于除 stdlib++ 之外的所有内容。
  • 当我插入集合时,我只插入不相交的对 - 即不与集合中当前存在的任何其他对相交的对。我希望(并且有点需要)能够使用与集合的当前成员不相交的范围进行搜索。我的另一个解决方案(当然)是从我的搜索范围的端点形成两个 Range 对象,并使用下端点作为 lower_bound 的输入和上端点作为上限的输入来搜索集合。
  • @DavidBien 如果...所有对都不相交。但在您的示例中,(97, 122) 与所有其他范围并不相交。这些要求不仅适用于范围内的元素,还适用于您作为键传递给equal_range() 的元素。
  • 完全正确 - 我正在更新在这种情况下设置的字母表。我有一个新的输入范围。我需要将其合并到现有的不相交范围集中。所以,我将做的,这是明确定义的,是使用lower_bound(97,97) 和upper_bound(122,122) 进行搜索。然后我有一系列元素,我将新的字母范围合并到其中。
  • @DavidBien 如果你违反了先决条件,那么它可以产生任何东西。拥有equal_range() 背后的一个想法是,它比lower_bound()upper_bound() 的两个独立调用更有效。毫不奇怪,两种不同的算法通常会产生不同的结果。
猜你喜欢
  • 1970-01-01
  • 2020-05-11
  • 2015-10-27
  • 2011-11-21
  • 1970-01-01
  • 2015-06-11
  • 2022-11-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多