Why std::optional is allowed to be compared to value? [duplicate]
Asked Answered
S

3

5

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?

Scribbler answered 29/4, 2021 at 15:55 Comment(12)
An empty optional compares less-than any (not empty) value. What would you rather it do?Declamation
@JDługosz As I said, I was expecting it to not even compileScribbler
This way you can have a container of std::optional, and sort it. Or otherwise use std::optional when ordering is expected.Averir
@nyarlathotep108: Why would you expect it to not compile?Canoness
<<not even compile>> you mean you are OK with == but question the utility of <?Declamation
@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.Scribbler
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.Barometrograph
@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.Canoness
@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.Scribbler
@nyarlathotep108: "Is a very implicit obscure behavior." I don't agree. There are only 2 answers if an unengaged optional<T> is to be comparable: less than all Ts, or greater than all Ts.Canoness
@NicolBolas Your last comment assumes the conclusion. It assumes that optional<T> is comparable with T which is what is being discussed. There is a third scenario, one where optional<T> could be non-comparable with T. Edit : this is resolved in your answer.Mathian
@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.Scribbler
A
4

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.

Admittance answered 29/4, 2021 at 16:9 Comment(1)
They could have provided default Compare function to the container when optional<T> is the key without making a global operator< with a default behaviorScribbler
C
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.

Canoness answered 29/4, 2021 at 16:18 Comment(14)
Sounds like an arbitrary decision. I'd rather not have arbitrary operators defined for typesBrine
@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.Canoness
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.Brine
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?Barometrograph
@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.Canoness
@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.Canoness
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).Brine
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<int> or other type.Barometrograph
@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<T> will also be a comparable type. Just like if you give it a move-only type T, optional<T> will also be a move-only type.Canoness
@NicolBolas It is not true that if a wrapper type Wrapper<T> must necessarily be compliant with all the operators which you can use on T. For example: you cannot still sum two std::optional<int> even if int supports operator+. Wrapper<T> 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.Scribbler
@nyarlathotep108: "Not even TypeScript allows such a comparison to take place." Well, C# does. So among statically-typed languages, it's hardly unknown to treat it as such. "It is not true that if a wrapper type Wrapper<T> 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.Canoness
@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.Scribbler
@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.Canoness
@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.Canoness
A
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.

Ambulacrum answered 29/4, 2021 at 16:17 Comment(1)
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.Scribbler

© 2022 - 2024 — McMap. All rights reserved.