Removing STL Global Operators != > <= >= ---------------------------------------- 26-Jan-95 by Nathan Myers X3J16/95-0018 Rogue Wave Software WG21/N0618 In Clause 20 we find definitions for a collection of global operators !=, >, <=, >=. These have been discussed in messages prior to and including c++std-lib- 2914 3180 3212 3227 3231 3239 3256 3258 3259 3260 3262 3264 3265 3266 3267 3269 3271 3272 3273 3274 3276 3277 3281 (notably:) 3282 3283 3284 3285 3289 3290 3299 3300 3301 3302 3303 3304 3306 3307 3308 3309 3310 3314 3320 3321 3327 and in C++std-ext-2743. Arguments against retaining them include: 1. Operator names are scarce and precious. These templates consume a severely restricted namespace. Any argument in favor of these templates applies equally for a global template defining operator+ in terms of operator+=. 2. The default definitions supplied assume a total ordering, which is wrong when only a partial ordering is meaningful. Supplying the definitions for a partial ordering can lead to overloading conflicts. 3. The definitions assume mathematical ordering, but other meanings are useful, and used commercially. Non-bool result types are common (universal, thus far). E.g. Rogue Wave uses < to indicate a proper subset, in one library, and to yield an expression object (not a bool) in another library. 4. The STL definitions break existing code which defines comparison templates, whether for partial order, total order, or non-order. On the reflector we have seen arguments which for the sake of brevity may be parodied (with appropriate apologies) as: a. The operators are in namespace std, so they are harmless. b. Partial specialization may fix everything, so we should not act yet. c. Name injection may fix everything, so we should not act yet. I parody the arguments because this is a proposal to fix the problems now. I think the conservative response is to fix them, first, and then take advantage of any latitude that language extensions or clarifications may give us, _after_ they are passed, and understood. My understanding of where resolutions on partial specialization, name injection, and interactions of namespaces and (partial) specialization are going suggests that while some of the problems mentioned will be solved, not all of them will be. Therefore, this proposal comes in two parts, to be decided separately. Removing STL Global Operators != > <= >= 26-Jan-95 Page 2 of 6 ---------------------------------------- 95-0018/N0618 1. Eliminate global operator templates !=, >, <=, >=. The definitions in STL cause problems that we may not be able to fix: it seems clear that they break code at least during users' transition to using STL. The conservative approach is remove them (at a cost in inconvenience for some) pending solutions to all the problems. 2. Add global template classes, and integrate them (by derivation) with the STL types. Our hope is that the following really solves all the problems; if so, we need a proposal ready to vote on. John Spicer assures us (c++std-ext-2743) that the technique is valid. The definitions are as follows (all in std::, of course): template struct equivalence { // assumes only == friend inline bool operator!=(const T& l, const T& r) { return !(l==r); } }; template struct partial_order { // assumes ==, <, >= friend inline bool operator!=(const T& l, const T& r) { return !(l==r); } friend inline bool operator >(const T& l, const T& r) { return r=l; } }; I have assumed >= because I see it used more than <= in C and C++ code. Requiring >= seems preferable to providing it based on (l>r || l==r), for efficiency reasons. The extra burden for a partial order is minimal. template struct total_order { // assumes only ==, < friend inline bool operator!=(const T& l, const T& r) { return !(l==r); } friend inline bool operator >(const T& l, const T& r) { return rs); } friend inline bool operator >=(const T& l, const T& r) { return !(r= and >/<=. Removing STL Global Operators != > <= >= 26-Jan-95 Page 3 of 6 ---------------------------------------- 95-0018/N0618 2. If one is writing a function that uses STL iterators, it is a great convenience to be able to use both == and != rather than restricting one's self to ==. The same argument applies to = and >/<=. By themselves, these two facts argue overwhelmingly for having templates that supply the missing operators automatically. However, 3. Defining <= automatically as the complement of > is not appropriate for all classes, even if those classes are sometimes used as STL iterators. Indeed, 4. Defining != automatically as the complement of == is not always appropriate either. For example, a. In IEEE floating point, a==b and a!=b are both false if a or b is NaN. b. I have seen classes that represent constraints, in which a==b is essentially a declaration that the value represented by `a' is the same as that represented by `b.' For such classes, a!=b and !(a==b) are totally different things. Now, one can argue that people who want a different meaning for != are free to define it differently. Indeed they are. Unfortunately, 5. The existence of a global operator!= template is likely to cause puzzling results when one uses != for a class for which == is not defined. Consider: struct Point { int x, y; }; void f(Point p, Point q) { p != q; } If operator!= is a global template, the likely effect, at least on some C++ implementations, will be to produce a diagnostic that operator!=(const Point&, const Point&) could not be instantiated, along with a pointer to someplace in the standard library. It may be hard to find that f is the real culprit. The trouble is that in effect, operator!= is declared and not defined; such things are always troublesome. Removing STL Global Operators != > <= >= 26-Jan-95 Page 4 of 6 ---------------------------------------- 95-0018/N0618 Moreover, 6. Suppose that the author of class Point does want == and != defined: struct Point { int x, y; }; bool operator==(const Point& p, const Point& q) { return p.x = q.x && p.y == q.y; } What does the author of Point now do to ensure that the user will see an appropriate operator!=? Unfortunately, the answer is not `nothing' because the availability of operator!= will depend on whether the USER included the appropriate STL header. Since Point has nothing to do with STL, it seems like a bad idea to force the user to include the STL header just so that operator!= will be defined. Still worse, 7. Since it is implementation-defined whether library headers include other library headers, it is implementation-defined whether the following will compile: #include struct Point { int x, y; }; bool operator==(const Point& p, const Point& q) { return p.x = q.x && p.y == q.y; } void f(Point p, Point q) { return p != q; } because it is implementation-defined whether includes the declaration of the template operator!=. Removing STL Global Operators != > <= >= 26-Jan-95 Page 5 of 6 ---------------------------------------- 95-0018/N0618 [Jerry Schwarz added this, in response to Alex Stepanov's remarks:] C++ is sometimes more subtle than it appears at first. Ideas that appear simple at first sometimes end up being very complicated. Whenever you generate operations you raise the possibility of creating ambiguities for what was previously unambiguous, and whenever you generate operations conditionally you have to be very careful about the conditions. Consider. class X ; class Y ; struct X { operator==(const X&) ; // operator!=(const X&) ; // assume this were generated } ; struct Y { Y(const X&) ; } bool operator!=(const Y&, const Y&) ; X x ; Y y ; x != y ; // without the generated operation this is // unambiguously a Y comparison. With the // generated operation it is either ambiguous // or the X operation. And it's very hard to know exactly when to generate the operation. Suppose you have one file with bool operator==(const X&, const X&) ; bool operator!=(const X&, const X&) ; and another file that only declares bool operator==(const X&, const X&) ; which != do you use in the second file, a generated one or the user defined one? The simplest solution is to simply say that x!=y is always defined as !(x==y) and forbid overloading !=. Even if that were a good idea (which I doubt) its clear it would break lots of code. Removing STL Global Operators != > <= >= 26-Jan-95 Page 6 of 6 ---------------------------------------- 95-0018/N0618 [And Dag Bruck added this:] My concern is that the templatized relational operators will break large amounts of existing code that makes no use of STL as soon as we introduce STL in some (other) part of the program. This is a problem for two reasons: we don't want to break existing code, and we don't want to make the adoption of STL hard. Here are two examples. (1) We have an application that didn't use STL, and now we're slowly moving to use STL. This means that we have an existing body of STL-unaware code, and some use of STL. // main.c #include "symbol.h" #include // just added .... // symbol.h struct symbol { .... }; bool operator != (symbol, symbol); // symbol.c bool operator != (symbol a, symbol b) { .... } This program is incorrect, as far as I can tell. Before the use of STL, symbol.h declared an operator and symbol.c defined it. After the use of STL, symbol.h declares a template specialization that will be implicitly generated from the templates. Defining a function with the same type as a template specialization in symbol.c is ill-formed. See 14.9.4 [temp.over.spec]. I believe this is a problem even if stl.h and symbol.h are never included in the same compilation unit. (2) The simple sollution is to remove symbol.c from the application. However, there is a significant risk that the operator was declared int operator != (symbol, symbol); It seems to me that this declaration is ill-formed if we have a templatized operator that returns bool.