On 28/11/2014 05:12, Gottlob Frege wrote:
On Wed, Nov 26, 2014 at 6:49 PM, Gavin Lambert
wrote: TBH, other than the "existing code" issue I'm not sure I like the idea of std::less working but op< not -- and in particular if you don't agree with the idea that "none is less than any other value" then I don't know why you'd agree with the idea that it should sort that way in a map, which is what you appear to be suggesting.
I don't like that std::less isn't the same as op< either, but it is the best we currently have. I hope to propose a fix for that - something like std::order, which map and set would use instead of std::less (and std::order would default to std::less for backwards compatibility).
I'm not sure I understand the meaning of having an order that isn't implied by op<. If op< is omitted because comparison is meaningless or otherwise confusing, how could it be well-defined how to sort them? If there is not a single well-defined sort order, then the library should not just pick one arbitrarily, and if there is, then why would it not be appropriate as an op
A good example of where std::order shouldn't be the same as op< would be std::complex. There isn't a sensible/natural mathematical way of answering whether one complex number is larger than the other (while at the same time getting the other properties we commonly expect from a less-than).
So std::complex shouldn't have op<. But it would be nice to use them in maps and other algorithms. (Sure, we now have unordered_map, but map/set still has uses.)
And std::complex, looked at as a pair
, does have a natural (lexicographical) ordering. So it would be nice if std::complex specialized std::order, but didn't have op<. If we don't get std::order, complex will probably specialize std::less in the near future, because it is what works today.
I don't agree with any of that. If you want to use it as a key for something map/set-like, use unordered_map/set. I can think of multiple possible orderings of std::complex depending on application need, and I don't think any one of them is inherently "better" than any other. So this should be left to the application to define the sort order, in the rare case that this would actually be needed.
But why do you want to have optional keys anyway? When does it make sense to have a single value attached to an empty key? It's rare for a dictionary type to permit null keys in any language.
optional<T> is Regular if T if Regular. The more that optional<T> "just works" like int works, the better. Particularly for generic code.
The first part sounds like a definition I am unfamiliar with. I agree with the second part, but I don't think it's relevant to this discussion. Nobody is talking about removing op<(optional<T>,optional<T>), which is what generic code would use. Any generic code that *could* use both T and optional<T> must by definition be aware of the fact that it's using optional, so will either know that op<(optional<T>,T) is not sensible and avoid using it or will explicitly convert as needed so that it invokes op<(optional<T>,optional<T>) instead. Alternatively it must be aware that it is using different types, so op< may not be sane between them. Either way I don't think this would be a problem in real code. However possibly there might need to be a type trait specialization that explicitly denies op<(optional<T>,T) and the reverse, to avoid confusing code that attempts to detect when op< does work between distinct types.
For less-than we have these competing points, (that each of us may weigh differently)
0 - std::less should be same as op< (I think we all agree here, actually, which is why we should have std::order)
1 - op<(optional<T>, optional<T>) is "sensible" but somewhat arbitrary 2 - op<(optional<T>, T) is questionable, often (usually?) wrong 3 - in general, func(optional<T>, optional<T>) should work if passed T's (ie implicit conversion is important) 4 - op<(optional<T>, optional<T>) *is* of the form func(optional<T>, optional<T>) - ie why should it be different?
All true.
2 and 3, to me, are the strongest, but directly opposed. How do you solve that? Either say
A1. "well operators are special, not typical functions" so we can poison them while keeping general functions implicit A2. 1 is on shaky ground, so let's remove both 1 and 2, but keep std::less/std::order, which was the main reason for having op<.
Of these, I prefer A1. I think it really is a special case. But I can understand why the present behaviour is as it is. I suspect those who dislike "none" having a defined order would go for A2 though. I would be ok with that as well, but I don't have a problem with this definition. But you're leaving out A3: remove 1+2 AND std::less/std::order. This means that optional types can't be used in map keys, only in unordered_map. I actually think that this is the best option, except that it has a greater potential to break existing code. Perhaps a predicate could be defined specific to optional giving the current ordering, so that users who do want that ordering or to continue using a map could just specify that predicate as a minimal fix. But I don't think that the predicate should be spelled "std::less". (When I define custom ordering for reference types in C#, which are nullable, I always specify that 'null' is smaller than any non-null value as well. The library Nullable type behaves similarly, although as I noted in another thread it does *not* support op< locally, you have to explicitly call a static comparison method to compare two Nullable values, or a Nullable and an implicitly promoted base value. This aligns best with A3+predicate.)