3

It is very useful to be able to compare for equality a std::optional<T> with T:

std::optional<int> opt_value;
int value = 123;

opt_value == value; // will always be 'false'

I think the behavior in this case is well defined and clear.

What I do not get is why is this allowed:

opt_value < value; // this will always be 'true'

I was expecting this to not even compile. I think it is very obscure what is happening here. What is the reason why this has been even added to the STL?

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
nyarlathotep108
  • 5,275
  • 2
  • 26
  • 64
  • An empty optional compares less-than any (not empty) value. What would you rather it do? – JDługosz Apr 29 '21 at 15:59
  • 2
    @JDługosz As I said, I was expecting it to not even compile – nyarlathotep108 Apr 29 '21 at 16:00
  • 1
    This way you can have a container of `std::optional`, and sort it. Or otherwise use `std::optional` when ordering is expected. – Some programmer dude Apr 29 '21 at 16:04
  • @nyarlathotep108: Why would you expect it to not compile? – Nicol Bolas Apr 29 '21 at 16:05
  • <> you mean you are OK with `==` but question the utility of `<`? – JDługosz Apr 29 '21 at 16:05
  • 1
    @JDługosz exactly, I think making `nullopt` always less then any other value is completely arbitrary. Since many of these not obvious default behavior normally do not get into the standard, I was wondering why this specific one could instead get in. – nyarlathotep108 Apr 29 '21 at 16:09
  • 1
    I don't understand what makes `opt_value == value` (for value types that support `operator==` any more semantically meaningful than `opt_value < value` (for value types that support `operator<`) with the possible exception that it feels more "obvious" that `nullopt_t != value` whereas it's not that intuitive what the ordering of a null optional and an actual value should be. But "empty optionals are less than all values" isn't that weird imo. – Nathan Pierson Apr 29 '21 at 16:10
  • @NathanPierson: "*I think making `nullopt` always less then any other value is completely arbitrary.*" So what? Just because it's arbitrary doesn't make it *wrong*. – Nicol Bolas Apr 29 '21 at 16:10
  • @Nicol Bolas I am not saying it is wrong of course. But is way less obvious than `operator==` and `operator!=`. Is a very implicit obscure behavior. Many proposal normally do not even get into the standard for more trivial reasons than this one. – nyarlathotep108 Apr 29 '21 at 16:15
  • 1
    @nyarlathotep108: "*Is a very implicit obscure behavior.*" I don't agree. There are only 2 answers if an unengaged `optional` is to be comparable: less than all `T`s, or greater than all `T`s. – Nicol Bolas Apr 29 '21 at 16:18
  • @NicolBolas Your last comment assumes the conclusion. It assumes that `optional` is comparable with `T` which is what is being discussed. There is a third scenario, one where `optional` could be non-comparable with `T`. Edit : this is resolved in your answer. – François Andrieux Apr 29 '21 at 16:19
  • @NicolBolas I can see why you see it that way, but I think nonetheless it is hugely debatable and a very arbitrary choice. Conceptually, to me it looks impossible being able to compare Nothing with Something. – nyarlathotep108 Apr 30 '21 at 09:35

3 Answers3

3

Short answer: map<optional<int>, int>
You want to be able to use optional<T> as a map key when T is usable as a key. Defining the empty state to be either less than or greater than the normal values makes it well behaved.

Meanwhile, comparing a plain T against an optional<T> should just, logically, upgrade the bare T to an optional<T> holding that value. So, providing an overloaded form that takes a bare T is just an optimization, and should have the same result.

JDługosz
  • 5,592
  • 3
  • 24
  • 45
  • They could have provided default Compare function to the container when optional is the key without making a global operator< with a default behavior – nyarlathotep108 Apr 30 '21 at 09:04
3

The proposal introducing optional to C++ states the conceptual idea of the type. An optional<T> is an object that augments the object type T with an additional value: nullopt. That's the idea of the type; it's a T that can have one extra value.

Given this reasoning, if T is ordered, then optional<T> should also be ordered. So the question now is not whether one should achieve this, but how to do so.

That answer will be arbitrary, but there are only two reasonable answers: either "not a T" is less than all values of T or it is greater than all values of T. They picked the former.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • 1
    Sounds like an arbitrary decision. I'd rather not have arbitrary operators defined for types – Aykhan Hagverdili Apr 29 '21 at 16:45
  • @AyxanHaqverdili: Define "less than" for a string in a way that isn't arbitrary. Pretty much *every* non-numeric comparison is, to one degree or another, arbitrary. – Nicol Bolas Apr 29 '21 at 16:47
  • 1
    That's also arbitrary, but that's legacy of C (strcmp already existed, so it made sense). Maybe we could do better in this century. – Aykhan Hagverdili Apr 29 '21 at 16:48
  • 1
    Of course for the vast majority of optional to optional comparisons it's not arbitrary at all, because it defers to the underlying comparison of the value type. Should all that be sacrificed just because it's a little bit arbitrary that `nullopt_t` is less than `0` instead of greater than or incomparable to `0`? – Nathan Pierson Apr 29 '21 at 16:48
  • @AyxanHaqverdili: My point is that having objects be able to be ordered has *value*. The order itself doesn't really matter. It doesn't matter if "A" is less than or greater than "a". What matters is that there is an answer to the question that is consistent. What matters is that I can take a bunch of strings and order them for easy and fast searching. Exactly what that order is doesn't matter, so long as one exists. – Nicol Bolas Apr 29 '21 at 16:49
  • @AyxanHaqverdili: My point is that being "arbitrary" is not a good enough reason not to do something. If it's "arbitrary" but extremely useful, then it should be done because being useful is what programming languages are for. – Nicol Bolas Apr 29 '21 at 16:51
  • 1
    It's weird that `nullopt` is less than `INT_MIN`. I do agree that there is value in being able to compare things, but a named comparator (`optional_cmp` ?) would have had the usefulness as well as not messing with people's expectations (you would know what you're signing up for). – Aykhan Hagverdili Apr 29 '21 at 16:53
  • 1
    It's not that weird to me. `INT_MIN` is smaller than all other `ints`, but we don't expect it to be smaller than `LONG_MIN` or `-DBL_MAX`. It's the smallest `int`, not the smallest `std::optional` or other type. – Nathan Pierson Apr 29 '21 at 16:57
  • @AyxanHaqverdili: You *do know* what you're signing up for. The class is not *hiding* its interface. It isn't tricking people into making things comparable. If you give it a comparable type `T`, `optional` will also be a comparable type. Just like if you give it a move-only type `T`, `optional` will also be a move-only type. – Nicol Bolas Apr 29 '21 at 17:15
  • @NicolBolas It is not true that if a wrapper type `Wrapper` must necessarily be compliant with all the operators which you can use on `T`. For example: you cannot still sum two `std::optional` even if `int` supports `operator+`. `Wrapper` and `T` are two different types in the type system and, with some well defined exceptions, you should always expect them to not be interchangeable. Not even TypeScript allows such a comparison to take place. I still surprised this got into the standard, knowing how strict they usually are. – nyarlathotep108 Apr 30 '21 at 09:20
  • @nyarlathotep108: "*Not even TypeScript allows such a comparison to take place.*" Well, [C# does](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/nullable-value-types). So among statically-typed languages, it's hardly unknown to treat it as such. "*It is not true that if a wrapper type Wrapper must necessarily be compliant with all the operators which you can use on T.*" That's only because of the complexity of making it work, not because of the lack of desire for it. What happens when you compare two optional is a much easier question than for addition. – Nicol Bolas Apr 30 '21 at 13:24
  • @NicolBolas as you can see from your C# link, in C# a comparison with null will always result in false, which means null < 5 will be false, unlike C++. Totally arbitrary. Should not be allowed. When allowed, I would still prefer what C# is doing. – nyarlathotep108 Apr 30 '21 at 13:50
  • @nyarlathotep108: You keep throwing the word "arbitrary" around like it's a meaningful argument rather than a personal value judgment. It's OK for different people to have different values about the utility of a construct. I've explained what the reasoning is; you don't have to agree with it to acknowledge that the reasoning is valid. – Nicol Bolas Apr 30 '21 at 13:57
  • @nyarlathotep108: For example, my values say that C#'s partial ordering rule is highly dangerous because, in the real world, nobody *ever* accounts for it. When NaNs show up in floating-point math, they almost always break code even though everyone has been aware of them for decades at this point. So from my perspective, partial ordering is the worst of both worlds: it's legal to compare them, but you can't rely on the answer. – Nicol Bolas Apr 30 '21 at 14:01
0

In essence it allow you to more easily compare values. With this you don't have to write:

opt_value.value() < value;

It also checks to make sure opt_value has a value.

Colbsters
  • 54
  • 2
  • I understand it can be practical, but I'd prefer to write more in this case. This is the whole point: I prefer being explicit than implicit in most cases, unless the behavior is super obvious. – nyarlathotep108 Apr 30 '21 at 09:11