52

The question is pretty clear. The following gives the reason why I think these expressions might yield undefined behavior. I would like to know whether my reasoning is right or wrong and why.

Short read:

(IEEE 754) double is not Cpp17LessThanComparable since < is not a strict weak ordering relation due to NaN. Therefore, the Requires elements of std::min<double> and std::max<double> are violated.

Long read:

All references follow n4800. Specifications of std::min and std::max are given in 24.7.8:

template<class T> constexpr const T& min(const T& a, const T& b);
template<class T> constexpr const T& max(const T& a, const T& b);
Requires: [...] type T shall be Cpp17LessThanComparable (Table 24).

Table 24 defines Cpp17LessThanComparable and says:

Requirement: < is a strict weak ordering relation (24.7)

Section 24.7/4 defines strict weak ordering. In particular, for < it states that "if we define equiv(a, b) as !(a < b) && !(b < a) then equiv(a, b) && equiv(b, c) implies equiv(a, c)".

Now, according to IEEE 754 equiv(0.0, NaN) == true, equiv(NaN, 1.0) == true but equiv(0.0, 1.0) == false we conclude that < is not a strict weak ordering. Therefore, (IEEE 754) double is not Cpp17LessThanComparable which is a violation of the Requires clause of std::min and std::max.

Finally, 15.5.4.11/1 says:

Violation of any preconditions specified in a function’s Requires: element results in undefined behavior [...].

Update 1:

The point of the question is not to argue that std::min(0.0, 1.0) is undefined and anything can happen when a program evaluates this expression. It returns 0.0. Period. (I've never doubted it.)

The point is to show a (possible) defect of the Standard. In a laudable quest for precision, the Standard often uses mathematical terminology and weak strict ordering is only one example. In these occasions, mathematical precision and reasoning must go all the way.

Look, for instance, Wikipedia's definition of strict weak ordering. It contains four bullet points and every single one of them starts with "For every x [...] in S...". None of them say "For some values x in S that make sense for the algorithm" (What algorithm?). In addition, the specification of std::min is clear in saying that "T shall be Cpp17LessThanComparable" which entails that < is a strict weak ordering on T. Therefore, T plays the role of the set S in Wikipedia's page and the four bullet points must hold when values of T are considered in its entirety.

Obviously, NaNs are quite different beasts from other double values but they are still possible values. I do not see anything in the Standard (which is quite big, 1719 pages, and hence this question and the language-lawyer tag) that mathematically leads to the conclusion that std::min is fine with doubles provided that NaNs are not involved.

Actually, one can argue that NaNs are fine and other doubles are the issue! Indeed, recall that there's are several possible NaN double values (2^52 - 1 of them, each one carrying a different payload). Consider the set S containing all these values and one "normal" double, say, 42.0. In symbols, S = { 42.0, NaN_1, ..., NaN_n }. It turns out that < is a strict weak ordering on S (the proof is left for the reader). Was this set of values that the C++ Committee had in mind when specifying std::min as in "please, do not use any other value otherwise the strict weak ordering is broken and the behavior of std::min is undefined"? I bet it wasn't but I would prefer to read this in the Standard than speculating what "some values" mean.

Update 2:

Contrast the declaration of std::min (above) with that of clamp 24.7.9:

template<class T> constexpr const T& clamp(const T& v, const T& lo, const T& hi);
Requires: The value of lo shall be no greater than hi. For the first form, type T shall be Cpp17LessThanComparable (Table 24). [...]
[Note : If NaN is avoided, T can be a floating-point type. — end note]

Here we clearly see something that says "std::clamp is fine with doubles provided that NaNs are not involved." I was looking for the same type of sentence for std::min.

It's worth taking notice of the paragraph [structure.requirements]/8 that Barry has mentioned in his post. Apparently, this was added post-C++17 coming from P0898R0):

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 (17.5.4) 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.

Which is a clear attempt to address the issue I'm raising here but in the context of concepts (and as pointed out by Barry, Cpp17LessThanComparable is not a concept). In addition, IMHO this paragraph also lacks precision.

Cassio Neri
  • 19,583
  • 7
  • 46
  • 68
  • 5
    [related](https://stackoverflow.com/questions/39966774/inhowfar-do-ieee754-floats-satisfy-lessthancomparable) – kmdreko Mar 14 '19 at 00:51
  • When the behavior is not defined it's because of possible runtime values. Some functions/languate features have a narrow contract (eg must not dereference a `nullptr`). And in these cases the programmers responsibility to exclude these cases. Since UB must not happen in `constexpr` context, I tried to put `std::min` in a `static_assert` with one parameter of `1.0/0`, and it didn't compiled, because I was unable to produce a NaN at compile time. I think if a requirement violation could be detected at compile time, it should simply fail the compilation. The wording is unfortunate anyway. – titapo Mar 14 '19 at 07:35
  • @M.M: IMHO, the situation that would make that most interesting would be a template where the "preferred" expansion would contain a constant expression of such a form but would also do something else that would jump the rails (e.g. access a null reference) but another expansion would behave in useful fashion. No version of the C or C++ Standard has made a serious effort to identify all corner cases where implementations should obviously behave in some particular fashion, but the precise wording of the Standard would indicate that those cases invoke UB, but that usually only breaks SFINAE. – supercat Mar 15 '19 at 14:59
  • 3
    The "duplicate" doesn't say whether or not the code in question is UB – M.M Mar 15 '19 at 22:19
  • 1
    Found the following paper that discusses this topic and its consequences on things like sorting: [Comparison in C++](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4367.html#Floating) – godel9 Mar 17 '19 at 17:23
  • "_NaNs are fine and other doubles are the issue_" That's absolutely correct. But only *when* you postulate it. No particular float value is the issue, only the particular S is. There is no requirement that the union of all S_i used at different times must satisfy the requirements. – curiousguy Mar 18 '19 at 06:08
  • @godel9 Thanks, this is useful indeed. Although the paper doesn't definitely answer the question above it certainly gives clues that the answer is positive (i.e., the behavior is undefined). – Cassio Neri Mar 18 '19 at 10:31
  • @CassioNeri How so? It says "_In the absence of NaN, `operator<` provides a weak order. This meets the requirements of `std::sort`_" So behavior is defined here. – curiousguy Mar 18 '19 at 15:58
  • Because, at least in theory, there are platforms where float point types do not have NaNs and the standard support them. For those types, `operator <` is a strict weak ordering (as the article says). On other platforms (e.g. x86_64) doubles follow IEEE 754 which guarantees the existence of NaNs and the quote you provide suggests that on these platforms `operator <` is not a strict weak ordering on doubles. This is in agreement with my point that (IEEE 754) doubles are not *Cpp167LessThanComparable* and the "Requires" element of `std::min` is violated. – Cassio Neri Mar 18 '19 at 16:42
  • In addition, if everything was fine with `operator <` the article would be meaningless. The article goes even further than the OP saying that other non float point types (integer types with one's complement and signed magnitude representations) also have issues regarding the strict weak ordering assumption of `operator <` and, consequently, regarding `std::min` as well. – Cassio Neri Mar 18 '19 at 16:48
  • 1
    The focus of this question on IEEE float seems unfortunately counterproductive as it's not really relevant to the actual question, and yet absorbs a lot of words. Could just as easily have done [something like this](https://godbolt.org/z/uwj_47) (which obviously isn't a strict weak ordering and doesn't require talking about NaNs or citing other standards to determine this). – Barry Mar 20 '19 at 14:16
  • @Barry, your approach is possible. Whether this is better is, IMHO, a question of taste. It introduces a new class with a unusual `operator <` that requires time (YMMV) to be understood. On the other hand, operations on float-point NaNs are better known and don't need to be much explained. It's true that referencing other Standards might deviate from the crux of the discussion. – Cassio Neri Mar 20 '19 at 14:47
  • @Barry: Also, look my Update 2. I believe that `double` and `NaN` come up very often during discussions on compliance (or lack of) with strict weak ordering assumption. I've started mentioning IEEE-754 after reading the paper suggested by @godel9 which reminded me the (at least theoretical) existence of float point types with no `NaN`. For the avoidance of doubt, I want to say that I totally agree with you that `double`s are not the (only) issue and they are just one example where the strict weak ordering is broken (the same paper mentions integer types with signed representation). – Cassio Neri Mar 20 '19 at 15:16

3 Answers3

13

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 ]];)

Community
  • 1
  • 1
Barry
  • 286,269
  • 29
  • 621
  • 977
  • +1 This answer greatly captures the intention and the direction the standard is taking (notice also the new paragraph [structure.requirements]/8). However, as things stand today (C++17) I feel DainsDwarf's answer is more correct. (Sure, I might be wrong.) Regarding, the question you posted as a comment in their post, my initial thought was that "it was obvious." Given the whole discussion we're having here, I no longer consider it obvious. The best I can say is "I don't know but I believe so" which isn't great. What I do know is that precision in this matter is becoming ever more important. – Cassio Neri Mar 20 '19 at 16:28
  • My apologies: I haven't realized that you mentioned [structure.requirements]/8 before I made update 2. I edited it to give credit where credit is due. – Cassio Neri Mar 22 '19 at 11:08
7

Disclaimer: I do not know the full C++ standard, I did research a bit about what was said about floats. I do know about IEEE 754-2008 floating-point numbers and C++.

Yes, you are right, this is undefined behavior by the C++17 standard.

Short read:

The standard does not say that std::min(0.0, 1.0); is undefined behavior, it says constexpr const double& min(const double& a, const double& b); is undefined behavior. It means, it's not applying the function that is not defined, it's the function declaration itself that is undefined. As is the case mathematically : a minimum function isn't possible on the full range of IEEE 754 floating-point numbers, as you have noted.

But undefined behavior does not necessarily mean a crash or compiling error. It just means it is not defined by the C++ standard, and specifically says it may "behaving during translation or program execution in a documented manner characteristic of the environment"

Why you shouldn't use std::min on doubles:

Because I realize the following long read section can get boring, here is a toy example of the risk of NaNs inside comparisons (I don't even try sorting algorithms…):

#include <iostream>
#include <cmath>
#include <algorithm>

int main(int, char**)
{
    double one = 1.0, zero = 0.0, nan = std::nan("");

    std::cout << "std::min(1.0, NaN) : " << std::min(one, nan) << std::endl;
    std::cout << "std::min(NaN, 1.0) : " << std::min(nan, one) << std::endl;

    std::cout << "std::min_element(1.0, 0.0, NaN) : " << std::min({one, zero, nan}) << std::endl;
    std::cout << "std::min_element(NaN, 1.0, 0.0) : " << std::min({nan, one, zero}) << std::endl;

    std::cout << "std::min(0.0, -0.0) : " << std::min(zero, -zero) << std::endl;
    std::cout << "std::min(-0.0, 0.0) : " << std::min(-zero, zero) << std::endl;
}

When compiling on my macbookpro with Apple LLVM version 10.0.0 (clang-1000.10.44.4) (I make the precision, because, well, this is undefined behavior, so this may in theory have different results on other compilers) I get:

$ g++ --std=c++17 ./test.cpp
$ ./a.out
std::min(1.0, NaN) : 1
std::min(NaN, 1.0) : nan
std::min_element(1.0, 0.0, NaN) : 0
std::min_element(NaN, 1.0, 0.0) : nan
std::min(0.0, -0.0) : 0
std::min(-0.0, 0.0) : -0

Which means that contrary to what you might assume, std::min is not symmetric when NaNs are involved, or even -0.0. And NaNs do not propagate. Short story: That did provoke me some pain on a previous project, where I had to implement my own min function to correctly propagate NaNs on both sides as was required by the project specification. Because std::min on doubles is not defined!

The IEEE 754:

As you have noted, IEEE 754 floating-point numbers(or ISO/IEC/IEEE 60559:2011-06, which is the norm used by C11 standard see below, which more or less copies IEEE754 for the C language) does not have a strict weak ordering, because NaNs violates the transitivity of incomparability (fourth point of the Wikipedia page)

The fun part is that the IEE754 norm has been revised in 2008 (now named IEEE-754-2008), which includes a total ordering function. The fact is that both C++17 and C11 do not implement IEE754-2008, but rather ISO/IEC/IEEE 60559:2011-06

But who knows? Maybe that would change in the future.

Long read:

First, let's start by recalling what undefined behavior actually is, from the same standard draft you linked (emphasis is mine):

undefined behavior behavior for which this document imposes no requirements

[Note 1 to entry: Undefined behavior may be expected when this document omits any explicit definition of behavior or when a program uses an erroneous construct or erroneous data. Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message). Many erroneous program constructs do not engender undefined behavior; they are required to be diagnosed. Evaluation of a constant expression never exhibits behavior explicitly specified as undefined in Clause 4 through Clause 14 of this document (7.7). —end note]

There is no such thing as "yielding" undefined behavior. It is simply something that is not defined in the C++ standard. Which may mean you may use it and get correct result at your own risk (like by doing std::min(0.0, 1.0); Or it might raise warning or even compilation errors, if you find a compiler that is really careful about floating-point numbers!

About the subset… You say:

I do not see anything in the Standard (which is quite big, 1719 pages, and hence this question and the language-lawyer tag) that mathematically leads to the conclusion that std::min is fine with doubles provided that NaNs are not involved.

I do not have read the standard myself either, but from the part you posted, it seems the standard already says this is fine. I mean, if you construct a new type T that wraps doubles excluding the NaNs, then the definition of template<class T> constexpr const T& min(const T& a, const T& b); applied to your new type will have a defined behavior, and behave exactly like you would expect from a minimum function.

We could also look at the standard definition of operation < on double, which is defined in the section 25.8 Mathematical functions for floating-point types which says the not really helpful:

The classification / comparison functions behave the same as the C macros with the corresponding names defined in the C standard library. Each function is overloaded for the three floating-point types. See also: ISO C 7.12.3, 7.12.4

What does the C11 standard says? (Because I guess C++17 does not use C18)

The relational and equality operators support the usual mathematical relationships between numeric values. For any ordered pair of numeric values exactly one of the relationships — less, greater, and equal — is true. Relational operators may raise the ‘‘invalid’’ floating-point exception when argument values are NaNs. For a NaN and a numeric value, or for two NaNs, just the unordered relationship is true.241)

As for the norm C11 uses, it is under the annex F of that norm:

This annex specifies C language support for the IEC 60559 floating-point standard. The IEC 60559 floating-point standard is specifically Binary floating-point arithmetic for microprocessor systems, second edition (IEC 60559:1989), previously designated IEC 559:1989 and as IEEE Standard for Binary Floating-Point Arithmetic (ANSI/IEEE 754−1985). IEEE Standard for Radix-Independent Floating-Point Arithmetic (ANSI/IEEE854−1987) generalizes the binary standard to remove dependencies on radix and word length. IEC 60559 generally refers to the floating-point standard, as in IEC 60559 operation, IEC 60559 format, etc.

DainDwarf
  • 1,651
  • 10
  • 19
  • 1
    This answer makes sense to me. A couple of points. 1) I confess I wasn't precise in my language and I do know that UB in this case means that the declaration of `std::min` is undefined (and not the expression `std::min(0.0, 1.0)`. But I must say that with this title the question is more catchy :-) – Cassio Neri Mar 20 '19 at 09:36
  • 2) +1 for the quote on what the standard says about undefined behavior. I do understand that. As I said in my update, we know the *intention* is that `std::min(0.0, 1.0)` should return `0.0`. Behaving in a "reasonable" way (which is not the same as "being specified") is also a possible outcome of a construct that has undefined behavior. – Cassio Neri Mar 20 '19 at 09:40
  • 3) The (probably) poor choice of words when I sa "yields undefined behavior" is much more related to my understand of English than C++. (I this is not too out-of topic, how would you express what I mean in a concise manner, adequate for the title? Obviously, please, feel free to edit my post.) – Cassio Neri Mar 20 '19 at 09:44
  • 4) Putting aside the *fact* (at least the two of us believe this is indeed a fact) that `std::min` is undefined, the fact that `std::min(1.0, NaN)` returns `1` and `std::min(NaN, 1.0)` returns `NaN` is intentional and is a sort of motivation for keeping the assumption of strict weak ordering. Indeed, as pointed out by Daniel Krugler [here](http://cplusplus.github.io/LWG/lwg-defects.html#2239). – Cassio Neri Mar 20 '19 at 09:50
  • 5) I've seen a post (sorry, I failed to find it again) where instead of implementing a replacement for `min` the author used a custom `comp` callable type that properly deals with `NaN`s making sure the induced order on `double` was, indeed, strict weak. – Cassio Neri Mar 20 '19 at 09:52
  • 5 (cont) The main difference between their `comp` induced order and the complete order defined by IEEE-754-2008 was that the former considered all `NaN` values as equivalent and thus it was a strict order but not a complete one. – Cassio Neri Mar 20 '19 at 10:02
  • 1) A catchy question can be good, that's probably why it was upvoted and in my weekly digest mail of SO. 2) Yes, that is the point, I don't know of a compiler/computer where `std::min(1.0, 0.0)` does not give `0.0`. But someone might come up with a strict IEEE754 compiler or compiler flag some day that return a compilation error ("terminating the translation", as standard C++ wording) – DainDwarf Mar 20 '19 at 10:07
  • 3) I don't know, maybe it is me who has a poor understanding of english, and yields might not refer to exception. 4) Well, that is an interesting discussion, thank you for the link. 5) Yes, that is a good solution. My real life example was using C++11, so I did not have that possibility, but that is a good addition of C++14, that I learned when answering your question. – DainDwarf Mar 20 '19 at 10:13
  • 1
    "The standard does not say that `std::min(0.0, 1.0);` is undefined behavior, it says `constexpr const double& min(const double& a, const double& b);` is undefined behavior." - that's an interesting claim, and one that's totally new to me. Do you have anything to back it up? – Barry Mar 20 '19 at 13:20
  • @Barry I don't know what to say more than what is in OP and in my answer: std::min requires the type T to have strict weak ordering relation, which is not the case for floating-point in C++17. The backing up is in my answer, from C++, C, and IEC 60559 standard. – DainDwarf Mar 20 '19 at 14:05
  • 1
    @DainDwarf float not having a total order isn't really in question (and really is an unfortunate focus of the question, since it also doesn't seem super relevant) - my question was more about the specific assertion about the _instantiation itself_ being UB as opposed to a specific use of it. – Barry Mar 20 '19 at 14:28
  • 1
    There are many functions which aren't defined (or well-behaved) over the whole domain of all their inputs. But not being defined for some parts does not mean they aren't defined and useful everywhere else! – Deduplicator Mar 20 '19 at 15:21
  • 1
    @Deduplicator: I think this answer is claiming that instantiating `std::min` with a type that doesn't meet the standard-specified requirement in UB. It might violate the standard, but there won't be UB in any specific library implementation. The real definition of `std::min(a,b)` I think has to be exactly equivalent to `(a – Peter Cordes Mar 27 '19 at 22:39
  • 1
    @PeterCordes Knowing `std::fmin` is useful and probably better than using `std::min`, but it does not propagate NaNs, it returns the non-NaN number instead. – DainDwarf Mar 28 '19 at 15:56
  • @DainDwarf: oops, you're right! I don't usually use it (because it's slower than `std::min`), and only remembered **that [`std::fmin`](https://en.cppreference.com/w/cpp/numeric/math/fmin) from is symmetric wrt. NaN**. (It does not propagate them unless both are NaN.) Deleted my previous wrong comment. – Peter Cordes Mar 29 '19 at 06:57
3

The only possible (not just plausible) interpretation is that the equations apply to values in the range of the function; that is to values actually used in the algorithms.

You might think of a type defining a set of values, but for UDT that wouldn't make sense anyway. Your interpretation of the range being every possible value of a type is patently absurd.

This is no problem here.

That might exist a very serious problem in implementations where a value of a floating point can have no more precision than allowed by the type, as the whole idea of a mathematical value of a floating point type loses all meaning, as the compiler may decide to change the value of a floating point type to remove precision at any time. In fact no semantics can be defined in that case. Any such implementation is broken and any program probably works only by accident.

EDIT:

A type doesn't define a set of values for an algorithm. This is obvious for user data types that have internal invariants that aren't formally specified in any code.

The set of values usable in any container, algorithm (containers internally use algorithms on elements)... is a property of that particular use of that container or algorithm. These library components don't have share their elements: if you have two set<fraction> S1 and S2, their elements won't be used by the other one: S1 will compare elements in S1, S2 will compare elements in S2. The two sets exist in different "universes" and their logical properties are isolated. The invariants hold for each one independently; if you insert in S2 an element x2 that is not less or greater than x1 in S1 (thus considered equivalent), you don't expect x2 to be found at the place of x1 in S1! There is no possible sharing of data structures between containers and elements can't be shared between algorithms (which can't have static variables of a template type as it would have unexpected lifetime).

Sometimes the standard is a riddle where you must find the correct interpretation (most plausible, most useful, most likely to be was intended); in case the committee members are asked to clarify an issue, they will settle on the most X interpretation (X = plausible, useful...) even if it contradicts the exact previous wording so when the text is obscure or gives crazy conclusions, you might as well skip the literal reading and jump to the most useful.

The only solution here is that every use of a templated library component is independent and that the equations only have to hold during that use.

You don't expect vector<int*> to be invalid because pointers can have invalid values that can't be copied: only a use of such value is illegal.

Thus

vector<int*> v;
v.push_back(new int);
vector<int*> v2 = v; // content must be valid
delete v[0];
v[0] = null; // during v[0] invocation (int*)(v[0]) has no valid value

is valid because the required properties of the element type are valid for the small duration where they are required to be.

In that case we can invoke a member function of a vector knowing that its elements don't respect the Assignable concept because there is no assignment allowed, as the no exception guarantee doesn't permit it: the value stored in v[0] cannot be used by v[0], there is no user defined operation on the element allowed in vector<>::operator[].

The library components can only use the specific operations mentioned in the description of the specific function on the values used in that invocation; even for a builtin type, it cannot make values in any other ways: a specific set<int,comp> instance may not compare values to 0 if 0 isn't inserted or looked up in a particular instance, as 0 may not even be in the domain of comp.

So builtin or class types are treated uniformly here. The library implementation cannot assume anything on the set of values even when instantiated with builtin types.

curiousguy
  • 8,038
  • 2
  • 40
  • 58
  • 2
    That may be the only possible interpretation but it would appear that we have to change/add some words in order to make it compatible with the wording of the standard. An editorial change may be called for here. – Lightness Races in Orbit Mar 17 '19 at 16:03
  • @LightnessRacesinOrbit Yes I find the whole style of specification of the STL lacking in rigor. But *less* lacking that other parts (atomics, lifetime issues). – curiousguy Mar 17 '19 at 16:50
  • @curiousguy: Any idea why the concept of `min` and `max` would be bound to operators like `>` and `<` rather than to `std::less`? – supercat Mar 17 '19 at 19:12
  • @supercat Most things in std::lib use `<` for comparison, it's mostly only the new ranges things that use `ranges::less` (the only difference really being pointers). – Barry Mar 17 '19 at 20:27
  • 2
    "Your interpretation of the range being every possible value of a type is patently absurd." I found rather the opposite, i.e., considering the set of "values actually used in the algorithms" is "patently absurd". Which values are those? The definition of strict weak ordering doesn't mention any algorithm. Please, see the update. – Cassio Neri Mar 18 '19 at 02:40
  • @supercat `min`/`max` were intended as "simple" replacement for MIN/MAX macros (`(x)<(y) ? (x) : (y)`); but nothing is really simple in C++! – curiousguy Mar 18 '19 at 04:35
  • @CassioNeri Those are the only values mentioned in the description of the function or class, those provided by the user or computed with functions allowed to be called on these values. – curiousguy Mar 18 '19 at 05:44
  • 1
    Intention is one thing, specification is another. The OP is about the latter and your post is about the former. I have no doubt of intention. Of course the specification is motivated by intention but it might have unexpected consequences. – Cassio Neri Mar 18 '19 at 09:49
  • 2
    @CassioNeri: No version of the C or C++ Standard has consistently employed the level of precision necessary to make it be a complete specification. Instead, all of them rely upon implementations to use common sense in filling in various gaps. The issue discussed here is tricky, though, because one could argue about whether a `double` should fit a template that expects a `Cpp17LessThanComparable`, or should force the compiler to try a different template. – supercat Mar 18 '19 at 14:13
  • 1
    @supercat Indeed and I don't say otherwise. But it's clear to me that the C++ committee try to be as precise as possible and they also rely upon users' feedback on the wording. They even have procedures in place for anybody to report any issue (include wording) they might find in the standard. "Perfection is not attainable, but if we chase perfection we can catch excellence." (Vince Lombardi). – Cassio Neri Mar 18 '19 at 14:32
  • @CassioNeri: The Standards *can't* employ the level of precision that would be necessary in a complete specification unless they either recognize various "popular extensions" and classify them as "optional features", or else make the languages unambiguously unsuitable for many purposes they're presently serving. There are many corner-case behaviors which are useful if not essential for some kinds of programs but useless for others, and cost nothing on some implementations but would be expensive on others. Any cost-benefit analysis for whether such behaviors should be supported... – supercat Mar 18 '19 at 15:10
  • ...by various implementations should consider the particulars of their target platforms and intended purposes--details the Standards committee can't possibly know. The solution would be to have a means by which a program could specify what semantics it requires for various things like integer overflow, type punning, etc.; implementations could either process the program using such semantics or refuse it altogether. The choice of what programs to accept would be a quality-of-implementation issue, but implementations that accept programs would be *required* to abide their demands. – supercat Mar 18 '19 at 15:21
  • @supercat As I wrote, any equations on floats are broken at least under popular compiler that can encode a value with additional precision then drop that precision (changing the value) at an unpredictable point, so even `x==+x` isn't guaranteed in non trivial code for fp variables with numeric non integral values. The std seems to accept the fact that fp math is non deterministic as the exact same sequence of operations can be compiled in different ways but offers no means to control that possible divergence. I believe that non trivial fp programs work by accident under such implementations. – curiousguy Mar 18 '19 at 15:24
  • @curiousguy: It seems fashionable to regard as "accidental" the fact that compiler writers historically sought to serve their customers needs even when the Standard would allow them to do otherwise. If a programmer is familiar with the quirks of a hardware platform where the natural method of evaluating `+x` might sometimes yield a slightly different value from `x`, but `+(+x)`, `+(+(+x))`, etc. would all yield the same value as `+x`, such a programmer might find an implementation where `x==+x` would sometimes return false if `+x` yielded a value different from `x` more useful... – supercat Mar 18 '19 at 17:37
  • @curiousguy: ...than one which generated much slower code because it always truncated everything. The authors of the Standard expected that compiler writers targeting commonplace targets would process many actions in commonplace fashion, but wanted to give those targeting unusual targets the ability to behave in ways that might serve their customers better than the commonplace behavior. They didn't think compiler writers needed to be told that when their customers are using commonplace hardware and want commonplace behavior, their compilers should behave in commonplace fashion. – supercat Mar 18 '19 at 17:43
  • @supercat The "problem" with `+x` is just an example where a value can be reloaded to/from memory. The issue in general is that the user cannot control when extended precision is going to be used, in a language intended as a high level assembly. – curiousguy Mar 18 '19 at 19:26
  • 1
    @curiousguy: If a particular implementation claims to be be suitable for tasks requiring a "high-level assembler", and portions of its documentation and/or the Standard describe the behavior of some constructs, it should process them as thus described even if other parts of the Standard would allow the implementation to do something else. The fact that a program behaves usefully on on an implementation that does that, despite the fact that the Standard doesn't require such behavior, would hardly be an "accident". – supercat Mar 18 '19 at 19:50
  • @supercat: You're exaggerating on my goal. I don't expect the committee to perfectly define the behavior of all C++ programs on all platforms. I understand the need of UB and the impossibility of completely removing it from C++. I'm just talking about `std::min` which is one of the simplest functions in the library. If its specification was "`std::min` returns `a – Cassio Neri Mar 20 '19 at 16:06
  • @CassioNeri We also want to be allowed to do `std::set` so just "fixing" `std::min` would not be sufficient. – curiousguy Mar 20 '19 at 16:50
  • @CassioNeri: Adding enough detail to the Standard to allow 99.99% of C programs to have fully-defined behavior would be impractical. At present, however, the level of detail is insufficient to allow fully-defined programs to accommodate even 0.001% of the tasks that are performed using freestanding implementations. One wouldn't have to add much detail to allow the majority of tasks to be accomplished via fully-defined programs [the Standard could specify the behavior of a "low-level implementation" as a sequence of loads and stores, with whatever consequences result on the target hardware]. – supercat Mar 20 '19 at 17:02
  • @CassioNeri: Otherwise, a major reason many things are left unspecified in the Standard is that it allow compiler writers that are trying to serve different customers to tailor their implementations to best sit their individual customers' needs. If compiler writers are presumed capable of recognizing and honoring their customers' needs, the only time the Standard should intervene would be in cases where the benefit of having all implementations do something in a consistent way that would be sub-optimal for some customers would exceed that of letting implementations serve customers optimally. – supercat Mar 20 '19 at 17:07
  • @supercat "_Adding enough detail to the Standard to allow 99.99% of C programs to have fully-defined behavior would be impractical_" I disagree. One *should* be able to formally prove the correctness of a real world program from written rules. – curiousguy Mar 20 '19 at 21:50
  • @curiousguy: It would probably be impractical to add enough detail to the Standard to allow 99.99% of C programs to have behavior fully specified *by the Standard*. On the other hand, the Standard should try to include enough detail that most programs' behavior would be specified, at least with regard to how they interact with the environment (though not necessarily how the environment will interact with the real world). The Standard need not concern itself with whether `*(uint32_t volatile*)0xC0001044 |= 8;` sounds a buzzer, turns on a light, or whatever, but should recognize... – supercat Mar 20 '19 at 22:06
  • ...a category of implementations, testable via `#ifdef` or other such means, where such a statement would perform a 32-bit load of CPU address 0xC0001044, OR the value with 8, and write the result back to that same address, rather than simply saying that the mapping of integers to addresses is "implementation-defined". – supercat Mar 20 '19 at 22:08