Subject: Re: [boost] [optional] operator<(optional<T>, T) -- is it wrong?
From: Matt Calabrese (rivorus_at_[hidden])
Date: 2014-11-28 03:37:45
On Thu, Nov 27, 2014 at 9:15 PM, Gavin Lambert <gavinl_at_[hidden]>
> On 28/11/2014 14:08, Vicente J. Botet Escriba wrote:
>> Le 27/11/14 23:44, Gavin Lambert a Ã©crit :
>>> 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<?
>> There are a lot of kinds of orders
>> (http://en.wikipedia.org/wiki/Total_order). Some of us would like that T
>> < T mean the same kind of order thing independently of T. As we have
>> already int < int that represents the natural order it follows that T <
>> T should mean then the natural order. We can then have a lot of other
>> orders that don't use this specific syntax. The order needed by the
>> ordered containers is not the natural order but a strict week order (
>> http://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings ).
> That doesn't address what I was referring to. My point was that if there
> is disagreement on where "none" should sort, or how "complex" should sort,
> then the library should not define these as default sorts (via op< or
> std::less). Instead it should either provide standard sorting predicates
> for the common options or just leave it up to the application to decide how
> it wants to sort things.
Why? I've heard this stated before regarding ordering and frankly I think
it's nonsense. Whenever you design a library you're going to make choices
that don't have an objective correct or incorrect option, even separate
from the issue of ordering. This can be anywhere from simple things like
naming or parameter order to the higher-level semantics of your types. The
library developer frequently makes subjective decisions and that's
perfectly fine -- I'd even say it's a good thing. The fact that there are
multiple valid solutions does not at all imply that you should avoid
choosing any of them, especially in this case where we are talking about a
default that doesn't even have to be used. If, in the case of a default
ordering, that default isn't what's desired for a specific situation, then
the user can just be explicit there. Do you have a problem with the fact
that tuples have a default ordering? What about strings? These are not
problematic in practice, nor, I'd argue, are they problematic in theory.
Just to make things a little more grounded with respect to optional and
variant, consider what happens when you provide a default ordering:
First, there are many cases where someone just wants a default ordering,
regardless of what that ordering may be (I.E. to use the type in a set), as
long as the default isn't doing something like creating unnecessary
equivalence classes. In this case, it doesn't matter that the library
designer chose the default. Any of these orderings are acceptable for the
user. Alright, so what if, in a particular case, the default ordering isn't
what a user personally wants? In that case, the programmer would just use
an explicit ordering when using I.E. set. Note that this case is no
different from what the user would have to do if no default ordering were
provided. Finally, what if someone sees the default ordering used in code?
Because it might not be immediately clear to the user, they'd simply look
up what the library specifies, assuming they need to or want to know. I
don't see how any of these situations are problematic.
Okay, so what happens if you reverse the situation and don't provide a
First, people can no longer use the type in datastructures like sets unless
they provide an explicit ordering, even if they don't care what the
ordering is. So here the user needs to do more to accomplish the same end
result. What exactly does the user gain? Is it just that if someone else
sees a default ordering used, they might have to investigate what that
default ordering is assuming they need to rely on it? If so, how is this at
all different from the user seeing any function used that they don't
immediately know the semantics of? In either case, all they'd do is look it
up. Just because the function happens to be called
std::order/std::less/operator< shouldn't make a difference.
As well, when you specify a default ordering, users can often take
advantage of it in a way that makes it applicable to their situation and
they retain the benefits of there being default. As an example of this, on
one occasion I used a variant to store the rank of a 5-card poker hand. In
other words I had the following:
variant<high_card, pair, two_pair, three_of_a_kind, straight, flush,
full_house, four_of_a_kind, straight_flush> rank;
In case it's not clear, each of those types has their own state, since, for
instance, not all three_of_a_kind are equal (the same is true for all of
the other types as well). Effectively, to order them appropriately, they
are ranked by their type first, and then by a homogeneous comparison if
they are the same type.
Because the variant template I used had specified that the default ordering
implies earlier-parameters are less than later-parameters, the default
behavior does exactly what I want. In fact, that's exactly why I specified
the parameters in that manner. I was able to take advantage of the default
because it was specified with the library. Okay, so what if the library
were designed in a way that used the reverse order (after all, either
choice is a valid ordering)? Is this "wrong?" No. In that case, all I would
have done was reversed the order of the arguments that I used with the
variant so that the default order made sense for my use-case. In either
situation I get what I want with very minimal work. Obviously there are
other, more contrived orderings, but these are the two obvious ones. The
point is, yeah, there is no "correct" choice for the default ordering, but
that's true of most decisions that are made when designing a library. That
in no way implies that the library should not make any choice at all.
Now, flipping things around, what would happen if the library designer said
"I don't want to choose a default"? As a user of the library, the only
effect is that I now need to do more work to accomplish my same goal for
any place that can take advantage of a default ordering. What exactly is
the tangible benefit here?
Of course, in absence of a poisoning method, if op<(optional<T>,
> optional<T>) and an implicit constructor both exist, then
> op<(optional<T>,T) will automatically exist even if not explicitly
> defined. Since the discussion related to explicit poisoning that didn't
> seem worth mentioning.
That's not true. Assuming that you are defining the operator as a template,
and not, for instance, as an inline friend of the class template, then the
implicit conversion there will not take place unless the template argument
is explicitly provided.
> Defining op< for containers was a necessary evil while std::map was the
> only standard associative container, and it happened to require ordered
> keys. Now that unsorted associative containers exist (std::unordered_map
> and its Boost equivalent for pre-C++11), I don't think its existence can be
> justified any more.
WHAT!? The choice of whether to use an ordered or unordered associative
container should have nothing to do with this. Period. They are entirely
different data structures with different complexities and the issue of
defaults is entirely orthogonal. However, while we're on the subject, I
think it's really important to point out that the unordered containers are
also taking advantage of a default that by your, and others', logic,
shouldn't exist -- the default hash. Just like with ordering, there is any
number of equally valid hashes that can be associated with a given type.
Why is one better than any other? I don't see why anyone would say that it
doesn't make sense to have a default ordering but yet it does make sense to
have, and rely on, a default hash.
-- -Matt Calabrese
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk