Re: [boost] Please don't define a fake "operator <" just forordered containers

Daryle Walker wrote:
??? Wrote
Because operator== has only one purpose in C++: comparison for equality. Operator<, on the other hand, has two common roles.
For classes that have natural ordering, it should define this ordering, and then all the other operators (> <= >=) should be defined as well. Classes that doesn't have this natural ordering, often still define operator< just to be able to be used in ordered containers. In such cases, operator< doesn't define a natural ordering because there isn't any. Instead, it just defines an ordering "that works" for ordered containers, nothing more. It doesn't need to compatible with operator== and !=, and defining operators >, <=, >= is misleading and should be avoided.
The later role is invalid. The reason is that the version of STL that made it into the Standard specifies an extra parameter for ordered containers for a comparison operation. It allows two containers of the same element type to use different comparison criteria (at compile- and/or run-time). This invalidates the need for a fake "operator <" since you can always package the comparison routine in an extra parameter.
Conversely, calling code must not hard code use of "operator <" agreed. or "std::less<>", disagree unless that code requires types with a natural ordering. (The standard algorithms have variants that either assume "operator <" or take in a comparison parameter.)
I think specializing std::less<> for types that don't have a natural order is /exactly/ the right way to enable such types to be stored in an associative container. The algorithms are a different case. sort doesn't make sense for a type that doesn't have a natural order, and in that case the user needs to provide an appropriate comparison.
Making a fake "operator <" just for ordered containers invalidates the work of those who added comparison parameters in standard classes/functions.
You have a point there, no doubt. On the other hand, Library writers should supply some kind of functor to be used with their types in conjunction with ordered containers (when appropriate, and boost::variant is of course appropriate). How should this functor be called? You might say there should be some convention, but there isn't any right now, and some libraries use operator< for this purpose. Is it that bad if operator< is the convention? I'm not sure. It's sure is convenient to use ordered containers like that... And it seems to me that there is no major drawback to this, because no other order operator is provided. But I guess it's highly dependent on taste...
The issue is different for the "==" and "!=" operators because two objects can have their state checked for equivalence without having a standardized order. The "std::complex<>" template classes are an example.
std::complex doesn't have operator<. std::tr1::shared_ptr does. So it seems the standard isn't very helpfully consistent here...
boost::shared_ptr is a good example of such a class with no natural ordering, with operator== and !=, with operator<, but without the other operators. And shared_ptr is of TR1 strength...
Ordering for pointers is generally defined if both pointers are part of the same array segment. (Either one or both of the pointers can be at the "one-past-the-end" point of said segment.) If two "shared_ptr" objects can be used for points in an array segment[1], then comparison can be allowed with ALL four operators ("<", ">", "<=", and ">="). Otherwise, none of those operators should be defined and any current existence of such operators should be considered a bug.
I didn't quite understand that last paragraph. shared_ptrs doesn't have to point to objects in array (actually, they better not), but there can be arrays of shared_ptrs. So should the shared_ptr class provide order operators or shouldn't it? Anyway, the committee already made up it mind about the answer to this question...
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
-- Martin Bonner Martin.Bonner@Pitechnology.com Pi Technology, Milton Hall, Ely Road, Milton, Cambridge, CB4 6WZ, ENGLAND Tel: +44 (0)1223 203894

Martin Bonner wrote:
I think specializing std::less<> for types that don't have a natural order is /exactly/ the right way to enable such types to be stored in an associative container.
map<K, V> will work when you have less<K>, but map< pair<K, int>, V > map< pair<K, K>, V > map< vector<K>, V > sort lower_bound will not.
The algorithms are a different case. sort doesn't make sense for a type that doesn't have a natural order, and in that case the user needs to provide an appropriate comparison.
The combination of sort and lower_bound makes sense as a map replacement, although specifying less<K>() here isn't hard.

On 7/6/06 5:20 AM, "Martin Bonner" <martin.bonner@pitechnology.com> wrote:
Daryle Walker wrote: [SNIP]
Conversely, calling code must not hard code use of "operator <" agreed. or "std::less<>", disagree unless that code requires types with a natural ordering. (The standard algorithms have variants that either assume "operator <" or take in a comparison parameter.)
I think specializing std::less<> for types that don't have a natural order is /exactly/ the right way to enable such types to be stored in an associative container.
Looking at this part of the Boost shared_ptr docs:
[Operator< has been preferred over a std::less specialization for consistency and legality reasons, as std::less is required to return the results of operator<, and many standard algorithms use operator< instead of std::less for comparisons when a predicate is not supplied. Composite objects, like std::pair, also implement their operator< in terms of their contained subobjects' operator<.
The rest of the comparison operators are omitted by design.]
in <http://www.boost.org/libs/smart_ptr/shared_ptr.htm>, even these authors agree with me that it's wrong to define "std::less<>" inconsistently with "operator <" (in this case by providing only the former and not the latter). I obviously disagree with them providing a fake "operator <" in the first place, though. If you are modeling data that has a standardized order, then it's OK to define (all) the ordering operators. If the order is something that you made up, and doesn't naturally flow with the model, then leave it out as a separate function (object). Don't shove in an ordering scheme via operators, especially if there more than one way to do it[1]. The Standard function and class templates provide ways to override the comparison routines, use them.
The algorithms are a different case. sort doesn't make sense for a type that doesn't have a natural order, and in that case the user needs to provide an appropriate comparison. [TRUNCATE]
[1] If you make a fake order for "std::complex<>", would you compare real components then imaginary components, or would you use magnitude then angle? -- Daryle Walker Mac, Internet, and Video Game Junkie darylew AT hotmail DOT com

Daryle Walker wrote:
[snipped] If you are modeling data that has a standardized order, then it's OK to define (all) the ordering operators. If the order is something that you made up, and doesn't naturally flow with the model, then leave it out as a separate function (object). Don't shove in an ordering scheme via operators, especially if there more than one way to do it[1].
The unspecified strict weak ordering defined by the operator< overload is independent from shared_ptr's template parameter. You say that there are more than one way to do it. Can you come up with another way? Keep in mind, your ordering must be well defined even for shared_ptr<void> objects. --Emil

On 7/11/06 1:47 AM, "Emil Dotchevski" <emildotchevski@hotmail.com> wrote:
Daryle Walker wrote:
[snipped] If you are modeling data that has a standardized order, then it's OK to define (all) the ordering operators. If the order is something that you made up, and doesn't naturally flow with the model, then leave it out as a separate function (object). Don't shove in an ordering scheme via operators, especially if there more than one way to do it[1].
The unspecified strict weak ordering defined by the operator< overload is independent from shared_ptr's template parameter. You say that there are more than one way to do it. Can you come up with another way? Keep in mind, your ordering must be well defined even for shared_ptr<void> objects.
The possibility of multiple definitions was posed as a weakness of the fake operator "<" technique in general, not of shared pointers in particular. (If all the potential definitions are unnatural, then why are you blessing one?) It shouldn't be construed as a challenge to find at least two definitions. The existence of exactly one unnatural, yet consistent, ordering doesn't validate the technique. -- Daryle Walker Mac, Internet, and Video Game Junkie darylew AT hotmail DOT com

Daryle Walker wrote:
[1] If you make a fake order for "std::complex<>", would you compare real components then imaginary components, or would you use magnitude then angle?
Real, then imaginary. Compare with: "If you make a FAKE order for std::string, would you compare left to right or right to left?"

On 7/11/06 6:17 AM, "Peter Dimov" <pdimov@mmltd.net> wrote:
Daryle Walker wrote:
[1] If you make a fake order for "std::complex<>", would you compare real components then imaginary components, or would you use magnitude then angle?
Real, then imaginary. Compare with:
It wasn't a challenge. It was just an example. I got the idea of it from _The C++ Standard Library_ book by Nicolai M. Josuttis, who asked help from David Vandevoorde on explaining why std::complex didn't have ordering. I looked at the "Comparison Operations" part of section 12.1.2. (In this section, Mr. Josuttis mentions the fake operator "<" trick, but warns against polluting the standard namespace when defining an operator for std::complex. He also mentions using a user-defined criterion.)
"If you make a FAKE order for std::string, would you compare left to right or right to left?"
You don't need to define a "fake" order since std::string already comes with ordering operators, and it comes with all of them. (Defining operator "<" and deciding to skip the ">", "<=", and ">=" operators is a code smell.) The ordering is lexicographic, which is left-to-right, assuming that's the direction you write your letters. If std::string didn't have ordering operators, I could provide comparison types for _both_ ways and let the user decide. If you decided to pick a direction for operator "<" by fiat, users that needed the other direction would have to make up a custom class anyway! -- Daryle Walker Mac, Internet, and Video Game Junkie darylew AT hotmail DOT com

Daryle Walker wrote:
If you make a fake order for "std::complex<>", would you compare real components then imaginary components, or would you use magnitude then angle?
1 < -2 or 100i < 1? Defining operator< for complex numbers is wrong, as there's no sensible definition. But for many types (such as smart pointers) there certainly is.
participants (5)
-
Daniel James
-
Daryle Walker
-
Emil Dotchevski
-
Martin Bonner
-
Peter Dimov