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

I think I have a basic understanding of your logic, but let's think about practicality for a moment. I'm not saying logical and philosophical reasons are not appropriate for this discussion, but let's try and not think about them for a second. I haven't heard mathematics described as "philosophical reasons" but okay. The ordering defined by the operator< overload for shared_ptr has the following important property: it is independent of shared_ptr's template parameter. You can order shared_ptr<void> objects, if you like. Or, a shared_ptr<void> and a shared_ptr<int>. I understand, total ordering is important and useful. The question is, what are the semantics of operator < ? If we fail to establish such principals then generic programming is a futile exercise. A generic algorithm is a theorem - it's correctness depends on a
collection of semantic requirements (Concepts), these are the axioms for the theorem.
Does it matter if share_ptr provides an operator <? No, but it isn't useful for generic code unless such an operation is tied to semantics which are generally accepted. T* does not provide such an operator < and I see no semantic difference between the two. The two should be consistent - I don't really much care which way we go but there are important semantics at play here for operator <. Specifically we're pinning down whether operator < should or should not guarantee a stable ordering across executions of the application. We can say that the C and C++ standard got the rules for T* wrong and lobby to change them, or that shared_ptr<> shouldn't have a less than operator. We might even still provide it under the claim that it shouldn't be defined but is so that std::less<> will work consistently with T* (assuming you don't want to just specialize std::less<> - which I think would be a better solution). Since we can't reasonably check semantic requirements, it is important for code correctness that we associate the requirements with the name of the operation. The signature is the only thing I can check.
Can you think of any problems that operator< could cause, in its current definition? In my mind, since you can't use T in your ordering semantics, you have no reasons to define your own ordering for shared_ptr. Therefore, you have no other use for operator<, and therefore if the one defined by the library causes no problems, we should keep it because it is practical. Not any realistic one - in general providing such an operator only breaks concept checks (when we are able to check if a function exists). But without consistent semantics it also doesn't allow me to develop any useful algorithms which rely on this.
Christoph Ludwig posted the correct reason why std::complex should not have an operator < () defined - sorry, my follow up using addition was poorly constructed.
What I am proposing is that std::less should be defined for std::complex.
Disagree. That could potentially break other template programming using std::less, to support std::unary_negate etc. Suddenly this would "work" for std::complex, when it shouldn't. Allowing code which otherwise would not be allowed is hardly breaking anything. I can see the argument that std::less<> should also be consistent with other operators. I think it is then important to define a general total order operation for this case and develop the rules. Please propose a set of rules for when operator<() should and should not be defined.
Why would we have a separate default relation for set/map then std::less? std::less is intended to map to operator <() on the type _or_ to a total ordering for the type if operator <() is not defined.
No, that's just for pointers. 20.5.7/8 says:
"For templates greater, less, greater_equal, and less_equal, the specializations for any pointer type yield a total order, even if the built-in operators <, >, <=, >= do not."
It doesn't mention std::complex or any other composite types. That still doesn't answer the question of why have another relation for set/map then std::less? And as for the intent for STL I can assure you that the intent of the designer was std::less was intended to provide a total ordering on all types. That standard also does not
----- prohibit us from specializing std::less for use defined types.
For pair<shared_ptr<>, int> - what makes shared_ptr special?
The fact that it is a pointer? I don't care much if it doesn't have an ordering. Then it shouldn't be different from T*.
std::less is supposed to be operator <, unless operator < is not defined in which case std::less provides a total ordering. It isn't ambiguous.
Where does the standard say that? I have the advantage of having Alex Stepanov on my team. I asked him. If you want to have an ordered collection of otherwise unordered data, the relation must be an application specific attribute. So define an appropriate functor, and use that with std::map, etc. If we want to introduce a third (or more) way to define a standard ordering other than operator <, std::less I'm fine with that. Just specify the semantics of them.
That is also how I interpret his intent based on what is currently in the standard, and that is why I maintain that *under the current standard*, the appropriate way to make a type ordered (even if only for the associatives) is via defining operator<. If we do this for shared_ptr it should be with a clear statement that we expect the rules for T* should be changed (or consider T* to be a legacy exception which cannot be changed).
Why would we have a separate default relation for set/map then std::less?
and:
As for what I expect std::greater<> et al. to do - I had that in my earlier 6 point note - they _better_ be defined consistently with std::less.
If the standard was written with this in mind, greater<>, less_equal<> and greater_equal<> would have been defined in terms of less<>. It is not reasonable to provide four customization points that the user is supposed to keep in sync. For me the fact that less_equal returns x <= y is strong evidence that it is not supposed to be a customization point, but a function object alias of operator<=. You can make the same arguments that operator < is not intended to be a customization point because the compiler doesn't automatically
For pair<shared_ptr<>, int> - what makes shared_ptr special?
Why should it be special? If a type K is ordered, pair<K, int> should also be. That isn't true for T*. I contend that either both should have operator < () defined or neither. I believe for shared_ptr is is
---- provide a consistent >, >=, <= (and == and !=). The standard is a legacy that must be dealt with - this should be done by providing a consistent set of rules and then lobbying for how the standard should change to be compliant. Ideally the rules should be as consistent with the standard as possible, but the important thing is that we are always striving for a correct solution. probably best to try to be consistent with T* then to lobby to get T* changed. Note that this likely still means that you want to define std::less on pair<> to be less of each of the elements. Either way, the standard is flawed. Sean

"Sean Parent" <sparent@adobe.com> skrev i meddelandet news:F32736FB-26E4-47A9-AF69-15CFCA587599@adobe.com...
-----
Christoph Ludwig posted the correct reason why std::complex should not have an operator < () defined - sorry, my follow up using addition was poorly constructed.
What I am proposing is that std::less should be defined for std::complex.
Disagree. That could potentially break other template programming using std::less, to support std::unary_negate etc. Suddenly this would "work" for std::complex, when it shouldn't. Allowing code which otherwise would not be allowed is hardly breaking anything.
It could, by allowing a change from typedef float value_t; to typedef std::complex<float> value_t; I would expect a failure for the later, and would be very surprised if it compiled. Also, if the code is using SFINAE to select overloads, there could be additional surprises.
I can see the argument that std::less<> should also be consistent with other operators. I think it is then important to define a general total order operation for this case and develop the rules. Please propose a set of rules for when operator<() should and should not be defined.
It should be defined when there is a general agreement on what it means. :-) In this specific case: If we agree that std::complex is a model of the math concept of complex numbers, we should not define operators that are not defined by the mathematicians. In other cases, there can be more than one natural order and it is not at all clear which one to choose. An example: struct person { string first_name; string last_name; string street_address; string zip_code; string city; string country; }; If I am printing address labels, the *application specific* sort order is zip_code, but I wouldn't really want operator< to sort on zip_code, would I. I definitely wouldn't want to sort on first_name either. In another application, the natural sort order could be last_name, or city, or country. Which one should we choose for the default operator<? Another example: struct person2 { string last_name; string first_name; }; Should the language standard specify that person2 is ordered on last_name, but the person struct above ordered on first_name? Definitely not!
Why would we have a separate default relation for set/map then std::less? std::less is intended to map to operator <() on the type _or_ to a total ordering for the type if operator <() is not defined.
No, that's just for pointers. 20.5.7/8 says:
"For templates greater, less, greater_equal, and less_equal, the specializations for any pointer type yield a total order, even if the built-in operators <, >, <=, >= do not."
It doesn't mention std::complex or any other composite types. That still doesn't answer the question of why have another relation for set/map then std::less? And as for the intent for STL I can assure you that the intent of the designer was std::less was intended to provide a total ordering on all types.
That might be so, but it's not how I understood it from the actual writing in the standard document. I always got the impression that std::less etc was a way to enable use of operators in template code. Unlike Ada generics, which use the actual operator strings, C++ syntax doesn't allow this, so an indirection was added. It doesn't say that we were supposed to add an order for unordered types. Pointers is an exception, but I see that as a late hack, not the general rule.
That standard also does not prohibit us from specializing std::less for use defined types.
True, but there is no requirement. Especially not where there is not one single obvious order.
For pair<shared_ptr<>, int> - what makes shared_ptr special?
The fact that it is a pointer? I don't care much if it doesn't have an ordering. Then it shouldn't be different from T*.
std::less is supposed to be operator <, unless operator < is not defined in which case std::less provides a total ordering. It isn't ambiguous.
Where does the standard say that? I have the advantage of having Alex Stepanov on my team. I asked him.
Too bad then, that the intention isn't obvious from the standard text.
If you want to have an ordered collection of otherwise unordered data, the relation must be an application specific attribute. So define an appropriate functor, and use that with std::map, etc. If we want to introduce a third (or more) way to define a standard ordering other than operator <, std::less I'm fine with that. Just specify the semantics of them.
I don't want to define a standard ordering for types that doesn't obviously have one. If I want to put my person records in sets, or sort them, I would define an application specific order for each particular case. std::set<person, order_by_name> map1; std::set<person, order_by_zip> map2; std::sort(people, order_by_shoe_size()); Bo Persson

Sean Parent wrote:
For pair<shared_ptr<>, int> - what makes shared_ptr special?
Why should it be special? If a type K is ordered, pair<K, int> should also be.
That isn't true for T*. I contend that either both should have operator < () defined or neither. I believe for shared_ptr is is probably best to try to be consistent with T* then to lobby to get T* changed.
I think that you either severely underestimate the amount of lobbying required to pass a change to the C subset of the C++ core language, or have an unique sense of humor. :-)
participants (3)
-
Bo Persson
-
Peter Dimov
-
Sean Parent