为什么在实现 max 模板时要使用 “b < a ? a : b” 而不是 “a < b ? b : a”?

160

C++模板 - 完全指南,第二版介绍了max模板:

template<typename T>
T max (T a, T b)
{
  // if b < a then yield a else yield b
  return  b < a ? a : b;
}

而且它解释了使用“b < a ? a : b”代替“a < b ? b : a”的原因:
注意,根据[StepanovNotes],max()模板有意返回“b < a ? a : b”而不是“a < b ? b : a”,以确保即使两个值相等但不相等,函数也能正确地运行。
如何理解“即使两个值相等但不相等。”?对我来说,“a < b ? b : a”似乎具有相同的结果。

9
在我看来似乎有问题...两个答案都是“正确”的,但如果ab等价的,那么!(a < b) && !(b < a)为真,因此a < bb < a都为假,所以在b < a ? a : b中,返回的是b,这不是你想要的...你需要的是a < b ? b : a - Holt
14
如果你反复执行 a = max(a, b);,你可能不想无谓地替换掉 a - Bo Persson
2
顺便提一下,这个模板应该通过const引用参数传递,并通过const引用返回它们,否则你会做很多无用的拷贝(并且你将用a的副本覆盖a)。 - Holt
3
具有等价和相等性的规范类型是CaseInsensitiveString。对于该类型,既不是a<A也不是A<a。但是std::addressof是无关紧要的。实际上,对于给定的“T max(T a, T b)”,我们已经知道addressof(a)!= addressof(b) - MSalters
2
你可以参考Stepano关于编程的笔记获取更多细节。我在阅读后发推文是因为解释不够详细。 - Shafik Yaghmour
显示剩余3条评论
3个回答

154

std::max(a, b)的确被指定在两个参数相等时返回a

这被Stepanov和其他人认为是一个错误,因为它破坏了有用的特性:给定ab,您总是可以使用{min(a, b), max(a, b)}对它们进行排序;因此,在参数相等时,您希望max(a, b)返回b


49
从这个链接中,“我很难责怪那些这样做的人:毕竟,他们只是遵循我编写的C++标准规范中的max。我花了几年时间才意识到我犯了错误。” - 哇! - Jack Aidley
23
你可以这样写 {min(a, b), max(b, a)},意思不变且更易理解。 - Captain Man
12
@CaptainMan: 是的,但这仍然不够明显。我认为,如果且仅如果min(a,b)返回b,max(a,b)返回a是有逻辑意义的,反之亦然,这样它们就是彼此的反向,并且(无序的)集合{min(a,b), max(a,b)}总是等于{a,b} - Jack Aidley
5
如果一个人例如要按时间对事件列表进行排序,那么他并不需要关心排序操作是否稳定,只需确保在输入中只出现一次的每个事件,在输出中同样只出现一次即可。某些操作(比如查找和消除重复事件)可能需要使用完全有序,但在许多其他情况下,可以接受以任意顺序列出同时发生的事件,但不能重复或省略它们。 - supercat
2
@supercat 在这种情况下,对除了时间戳(排序键)以外的任何东西应用minmax都没有意义。如果相等不意味着可互换,那么事件(对象)本身甚至不应该是可比较的。仅当对象是可互换的时,{min(a, b), max(a, b)}作为一种排序机制才有任何意义。 - jpmc26
显示剩余7条评论

62

这个答案从C++标准的角度解释了为什么给定的代码是错误的,但它缺乏背景信息。

请查看@T.C.的答案,以获取有关上下文的解释。


该标准将std::max(a, b)定义如下[alg.min.max](重点是我的):
template<class T> constexpr const T& max(const T& a, const T& b);

Requires: Type T is LessThanComparable (Table 18).

Returns: The larger value.

Remarks: Returns the first argument when the arguments are equivalent.

这里的“等价”意味着!(a < b) && !(b < a)true[alg.sorting#7]

特别地,如果ab是等价的,则a < bb < a都为false,因此条件运算符中右侧的值将被返回,所以a必须在右侧,因此:

a < b ? b : a

...似乎是正确的答案。这是libstdc++libc++使用的版本。

因此,根据当前标准,您引用中的信息似乎是错误的,但定义它的上下文可能不同。


4
Godbolt链接解释了这个问题(感谢@songyuanyao对X的定义)。 - Holt
1
@JackAidley 我已经编辑了答案,以指明推理的目标是当前的标准。 - Holt
@codekaizer,我实际上是在引用“如果我们将equiv(a, b)定义为!comp(a, b)&&!comp(b, a)”这句话。我已将链接更改为更好的引述(在标准中的第三行...)。 - Holt
1
很惊讶没有人提到浮点数,其中 a<bb<a 都可能为假,因为它们是无序的(一个或两个都是 NaN,所以 == 也是假的)。这可以被看作是一种等价关系。与此有关:x86 的 maxsd a,b 指令实现了 a = max(b,a) = b < a ? a : b。(在 x86 上提供无分支 FP min 和 max 的指令是什么?)。该指令保持源操作数(第二个操作数)无序,因此对数组的循环将在存在任何 NaN 的情况下给出 NaN。但是,max_seen = max(max_seen,a[i]) 将忽略 NaN。 - Peter Cordes
1
另请参阅斯特潘诺夫的编程笔记 - Shafik Yaghmour

22

当两个值相等时,应该返回第一个参数a;因此,std::max在这种情况下必须返回a

如果它们相等,则返回a

因此,应该使用a < b ? b : a;另一方面,b < a ? a : b;会错误地返回b

(正如@Holt所说,引文似乎相反。)

"这两个值是等效的但不相等"的意思是当进行比较时它们具有相同的值,但在其他方面可能是不同的对象。

例如:

struct X { int a; int b; };
bool operator< (X lhs, X rhs) { return lhs.a < rhs.a; }
X x1 {0, 1};
X x2 {0, 2};
auto x3 = std::max(x1, x2); // it's guaranteed that an X which cantains {0, 1} is returned

1
请问您能否详细说明为什么当ab相等时,std::max(a, b)必须返回a - JFMR
4
“这只是标准做出的任意选择。”尽管这是否是一个好的选择还有一些争议。 - StoryTeller - Unslander Monica
这是不是与问题相矛盾?如果 ab 是等价的,那么 !(a < b) && !(b < a) 就是真的,所以 a < bb < a 都是假的,那么...? - Holt
2
@ネロク 我想标准只是想让它确定;当它们等效时应该返回哪一个。 - songyuanyao
1
感谢您提醒我为什么一个对象可能是“等效”的,但不是“相等”的。 - Jeff Walker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接