【问题标题】:Strange discrepancy between library reference and compiler for std::upper_bound()std::upper_bound() 的库引用和编译器之间的奇怪差异
【发布时间】:2015-08-29 01:24:31
【问题描述】:

我需要处理一组Foo 类型的对象列表,这些对象共享质量对应于Bar 的相同值。该列表是根据该质量预先排序的,所以我的想法是使用std::upper_bound 来查找后续组的开始位置。

Bar FooToBar(const Foo &foo);
// sorted so that FooToBar(foolist[0] <= FooToBar(foolist[1]) <= ...
std::list<Foo> foolist; 

// find bounds of a group of Foo-s corresponding to someBar;
Bar someBar;
auto 
    groupBegin = foolist.begin(),
    // find last item of foolist whose FooToBar() == someBar
    groupEnd   = std::upper_bound( foolist.begin(), 
                                   foolist.end(), 
                                   someBar ); 

这当然行不通,因为FooBar 不能直接比较。幸运的是,std::upper_bound 的重载需要一个额外的比较器参数:

groupEnd = std::upper_bound( foolist.begin(), foolist.end(), someBar, Compare);

问题是,我该如何写Compare()?这就是事情变得有趣的地方。 cppreference.com 说:

比较函数的签名应该等价于:

bool cmp(const Type1 &a, const Type2 &b);

签名不需要有const &,但函数对象不能修改传递给它的对象。 类型 Type1 和 Type2 必须使得 T 类型的对象可以隐式转换为 Type1 和 Type2,并且 ForwardIt 类型的对象可以取消引用,然后隐式转换为 Type1 和 Type2。​

显然,我无法用FooBar 满足这些条件。但是,cplusplus.com 说的有些不同:

接受两个参数的二进制函数(第一个始终是 val,第二个是 ForwardIterator 指向的类型),并返回一个可转换为 bool 的值。

我可以使用它,所以:

bool Compare(const Bar &bar, const Foo &foo) { /* ... */ }

但是,这在 VS2013 和 g++ 中都无法编译:

/usr/lib/gcc/x86_64-pc-cygwin/4.9.2/include/c++/bits/predefined_ops.h:141:37: 错误:无法在参数传递中将“Foo”转换为“Bar”

奇怪的是,当我颠倒参数顺序时,它会编译、运行并按预期运行:

bool Compare(const Foo &foo, const Bar &bar) { /* ... */ }

所以看起来一个引用说一件事,另一个引用说另一件事,编译器接受的东西仍然不同。还是我误会了什么?

【问题讨论】:

  • 请注意,比较的顺序或参数类型在lower_boundupper_bound 之间是不同的。
  • 是的,cppreference 的Compare 模板已损坏。许多算法确实要求比较器可以双向调用,但lower_boundupper_bound 不需要。
  • @T.C.也许它应该被简化为比较概念的链接,更精确

标签: c++ stl c++-standard-library


【解决方案1】:

您指的是标准中的缺陷:#270。原始措辞被认为是严格的(实际上,提到了您的特定用例)。标准中的部分现在改为 [upper.bound]:

template<class ForwardIterator, class T>
  ForwardIterator
    upper_bound(ForwardIterator first, ForwardIterator last,
                const T& value);

template<class ForwardIterator, class T, class Compare>
  ForwardIterator
    upper_bound(ForwardIterator first, ForwardIterator last,
                const T& value, Compare comp);

要求[first,last) 的元素 e 应根据表达式 !(value &lt; e)!comp(value, e) 进行分区。
返回:在[first,last] 范围内的最远迭代器i 使得对于[first,i) 范围内的每个迭代器j 都满足以下相应条件:!(value &lt; *j)comp(value, *j) == false

在这两种情况下,valuecomp 的第一个参数,元素是第二个。所以以下是完全有效的代码:

struct Foo { };
struct Bar { };

std::vector<Foo> foolist;

auto it = std::upper_bound(foolist.begin(), foolist.end(), Bar{}, 
                           [](Bar const&, Foo const&) { return false; });

以上内容适用于 gcc 5.2(甚至 4.6.4——以 lambda 为模——这是我可以轻松访问的最古老的版本)和 clang 3.6。

【讨论】:

  • 只剩下我必须使用反转参数顺序来编译它的事实,需要一个答案。不幸的是,我现在手头没有这个代码,我不能再重现它了。对我来说可能是一个愚蠢的错误,但我一定会调查并报告。
  • @neuviemeporte 这是编译器错误。 Bar 应该是 upper_bound 的第一个参数(但 lower_bound 的第二个参数)。
  • 你可能是对的,我正在调查这两个,我一定在某个时候把它们弄混了。那就接受。 :)
【解决方案2】:

如果您阅读了您在问题中引用的文档部分,您将了解,除非存在从 BarFoo 的隐式转换,否则 Compare 的两个版本都不正确。一个版本有效的事实仅仅是一个幸运的巧合。使用不同的编译器可能很容易失败。

【讨论】:

  • 你确定吗? @Barry 提供的链接显示了如何有意更改标准以允许不存在直接转换的情况。而且我看不出我是多么幸运,我的原始代码中的类型完全不相关(原始指针列表与不相关的结构)。
  • @neuviemeporte,根据您引用的文档,我敢肯定。当标准修复了文档并且供应商实施了新行为时,您的函数应该可以工作的第一个版本,而第二个版本不应该。
  • 好的,那么我们如何判断是领先的标准还是落后的文档,以及在什么时候可以安全地在本应可移植的代码中使用新版本?
  • 如果您能够将BarFoo 进行比较,那么这两种类型之间一定有一些共同点。你能从someBar创建一个临时的Foo并使用临时的Foo吗?
  • 不,我不能。但是看看 VC++ 头文件中lower_bound() 的实现,没有什么需要转换才能工作——它只是用!_Pred(_Val, *_Mid) 调用谓词。这证实了@Barry 的说法——他的FooBar 也尽可能不相关。
猜你喜欢
  • 1970-01-01
  • 2022-01-16
  • 1970-01-01
  • 2020-10-16
  • 1970-01-01
  • 2019-04-30
  • 1970-01-01
  • 2011-11-21
  • 2021-10-05
相关资源
最近更新 更多