ChatGPT解决这个技术问题 Extra ChatGPT

Why use “b < a ? a : b” instead of “a < b ? b : a” to implement max template?

C++ Templates - The Complete Guide, 2nd Edition introduces the max template:

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

And it explains using “b < a ? a : b” instead of “a < b ? b : a”:

Note that the max() template according to [StepanovNotes] intentionally returns “b < a ? a : b” instead of “a < b ? b : a” to ensure that the function behaves correctly even if the two values are equivalent but not equal.

How to understand "even if the two values are equivalent but not equal."? “a < b ? b : a” seems have the same result for me.

Looks wrong to me... Both answers are "correct", but if a and b are equivalent, then !(a < b) && !(b < a) is true, so a < b and b < a are both false, so in b < a ? a : b, b is returned, which is not what you want... You want a < b ? b : a.
If you do a = max(a, b); (repeatedly) you might not want to replace a unnecessarily.
BTW this template should take parameters by const-references and return them by const-reference, otherwise, you are doing a bunch of useless copies (and you are going to override a with a copy of a).
@Caleth: The canonical type which has both equivalence and equality is the CaseInsensitiveString. For that type, neither a<A nor A<a. But std::addressof is irrelevant. In fact, for the given T max(T a, T b) we already know addressof(a) != addressof(b).
You can refer to Stepano'v Notes on Programming for more details I tweeted about this after reading that because the explanation was not detailed enough.

T
T.C.

std::max(a, b) is indeed specified to return a when the two are equivalent.

That's considered a mistake by Stepanov and others because it breaks the useful property that given a and b, you can always sort them with {min(a, b), max(a, b)}; for that, you'd want max(a, b) to return b when the arguments are equivalent.


From that link "It is hard for me to blame people who do so: after all, they just follow the C++ standard specification of max written by me. It took me several years to see that I was mistaken." - wow!
can't you just do {min(a, b), max(b, a)}?
@CaptainMan: Yes, but it's still less obvious. I would argue that it makes logical sense that max(a,b) will return a if-and-only-if min(a,b) returns b, and vice versa so that they are the reverse of each other and the (unordered) set {min(a,b), max(a,b)} is always equal to {a,b}.
@jpmc26: If one is e.g. sorting a list of events by time, one need not to care about whether the sorting operation is stable to care about having each event that appears exactly once in the input, likewise appear exactly once in the output. Certain operations (like finding and eliminating duplicated events) may require using a complete ordering, but in many other cases, it may be acceptable to list simultaneous events in arbitrary order, but not to duplicate or omit them.
@supercat Applying min and max to anything but the timestamp (the sort key) in that scenario makes no sense. The events (the objects) themselves shouldn't even be comparable if equality doesn't imply interchangeability. The only way {min(a, b), max(a, b)} makes any sense as a sort mechanism is if the objects are interchangeable.
C
Community

This answer explains why the given code is wrong from a C++ standard point-of-view, but it is out of context.

See @T.C.'s answer for a contextual explanation.

The standard defines std::max(a, b) as follows [alg.min.max] (emphasis is mine):

template 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.

Equivalent here means that !(a < b) && !(b < a) is true [alg.sorting#7].

In particular, if a and b are equivalent, both a < b and b < a are false, so the value on the right of : will be returned in the conditional operator, so a has to be on the right, so:

a < b ? b : a

...seems to be the correct answer. This is the version used by libstdc++ and libc++.

So the information in your quote seems wrong according to the current standard, but the context in which it is defined might be different.


Godbolt link that explains the issue (thanks @songyuanyao for the definition of X).
@JackAidley I've edited the answer to specify that the reasoning targets the current standard.
@codekaizer I was actually referring to "If we define equiv(a, b) as !comp(a, b) && !comp(b, a)". I've changed the link to a better quote (3 lines below in the standard... ).
Surprised nobody has mentioned floating point, where a<b and b<a can both be false because they're unordered (one or both NaN, so == is false too). That could be seen as a sort of equivalence. loosely related: x86's maxsd a, b instruction implements a = max(b,a) = b < a ? a : b. (What is the instruction that gives branchless FP min and max on x86?). The instruction keeps the source operand (the 2nd one) on unordered, so a loop over an array will give you NaN if there were any NaNs. But max_seen = max(max_seen, a[i]) will ignore NaNs.
s
songyuanyao

The point is which one should be returned when they are equivalent; std::max has to return a (i.e. the first argument) for this case.

If they are equivalent, returns a.

So a < b ? b : a should be used; on the other hand, b < a ? a : b; will return b incorrectly.

(As @Holt said, the quote seems opposite.)

"the two values are equivalent but not equal" means they have the same value when being compared, but they migth be different objects at some other aspects.

e.g.

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

Could you please elaborate on why std::max(a, b) has to return a, if a and b are equivalent?
@ネロク - It's just an arbitrary choice on the standard's part. Though it's a bit debatable if it's a good one.
Is it just me or is this in contradiction with the question? If a and b are equivalent, then !(a < b) && !(b < a) is true, so a < b and b < a are false, so... ?
@ネロク I suppose the standard just wants to make it determine; when they're equivalent which one should be returned.
thanks for refreshing my memory on why an object would be "equivalent" but not "equal".