Do std::min(0.0, 1.0) and std::max(0.0, 1.0) yield undefined behavior?

In the new [concepts.equality], in a slightly different context, we have:

An expression is equality-preserving if, given equal inputs, the expression results in equal outputs. The inputs to an expression are the set of the expression’s operands. The output of an expression is the expression’s result and all operands modified by the expression.

Not all input values need be valid for a given expression; e.g., for integers a and b, the expression a / b is not well-defined when b is 0. This does not preclude the expression a / b being equality-preserving. The domain of an expression is the set of input values for which the expression is required to be well-defined.

While this notion of the domain of an expression isn’t completely expressed throughout the standard, this is the only reasonable intent: syntactic requirements are properties of the type, semantic requirements are properties of the actual values.

More generally, we also have [structure.requirements]/8:

Required operations of any concept defined in this document need not be total functions; that is, some arguments to a required operation may result in the required semantics failing to be satisfied. [ Example: The required < operator of the StrictTotallyOrdered concept ([concept.stricttotallyordered]) does not meet the semantic requirements of that concept when operating on NaNs. — end example ] This does not affect whether a type satisfies the concept.

This refers specifically to concepts, not to named requirements like Cpp17LessThanComparable, but this is the right spirit for understanding how the library is intended to work.


When Cpp17LessThanComparable gives the semantic requirement that

< is a strict weak ordering relation (24.7)

The only way for this to be violated is to provide a pair of values which violate the requirements of a strict weak ordering. For a type like double, that would be NaN. min(1.0, NaN) is undefined behavior – we’re violating the semantic requirements of the algorithm. But for floating points without NaN, < is a strict weak ordering – so that’s fine… you can use min, max, sort, all you like.

Going forward, when we start writing algorithms that use operator<=>, this notion of domain is one reason that expressing a syntactic requirement of ConvertibleTo<decltype(x <=> y), weak_ordering> would be the wrong requirement. Having x <=> y be partial_ordering is fine, it’s just seeing a pair of values for which x <=> y is partial_ordering::unordered is not (which at least we could diagnose, via [[ assert: (x <=> y) != partial_ordering::unordered ]];)

Leave a Comment

tech