Previous | Table of Contents | Next |
8.3.5.2. Requiring Particular Functions
Suppose we want to state a requirement that t1<t2 be defined, where t1 and t2 are of type T. At first, it might seem sufficient to require that T::operator<(T) be defined. Unfortunately, that requirement is not nearly good enough. Also not good enough is the approach of requiring T to be derived from some other class, perhaps called comparable, that has an appropriate operator< defined.
For starters, we would like to cater to the possibility of defining T::operator<(const T&) instead; such a definition is probably more likely in practice than T::operator<(T). Other possibilities include operator<(T, T) and operator<(const T&, const T&). These possibilities may well be more realistic than either of the member functions because it is generally a good idea to avoid using member functions for operators that, like <, tend to treat their operands symmetrically for conversion purposes.
Another possibility is that T might be a built-in type, in which case < is a built-in operator and does not have a function that represents it at all. Moreover, there is no way to derive a built-in type from any particular base class.
Yet another possibility is that comparison between objects of type T might be defined in terms of any of the whole collection of functions of the form operator<(const S1&, const S2&), where S1 and S2 are either T or public base classes of T.
If those were not enough, there is also the possibility that < might not be defined on objects of type T at all, but that there is a user-defined conversion that takes T into some other type (or types) for which < is defined.
Even if an appropriate function is defined, there might be more than one such function. That would render expressions such as t1<t2 ambiguous, even though type T, when considered in isolation, might have every relevant function defined.
On the other hand, if the body of a template function uses the expression t1<t2 and that template function is instantiated, the program does not compile unless t1<t2 is uniquely defined. Therefore, the mere presence of t1<t2 in a function body is a sufficient requirement all by itself.
8.3.5.3. Doing the Right Thing
Even if we had an explicit way of stating the requirement that our type T have t1<t2 defined, that is not enough. It is also necessary for < actually to behave like a comparison. Typically, classes such as set maintain their elements in some kind of data structure that relies on ordering to implement fast searching. If < does not behave appropriately, the whole data structure collapses.
The problem of requiring appropriate behavior for operators becomes more acute when those requirements include names for which there is no universally accepted meaning. For example, most programmers assume that a class that defines < does so as an order relation of some kind, but what about +? Addition is not the only operation that classes use + to express. If a container requires its elements to have + defined, having a way to state that requirement in the language doesnt strengthen the specification much at all, unless there is also a way to talk about what + does.
Curiously, requiring that < represent an order relation is often too strong in practice; instead, it is important to require only that < represent an order relation over the elements that are stored in each particular container. It is desirable to loosen the requirement on < because some types have singular values, often used to represent error conditions, that do not obey the normal rules for comparison. These types are still appropriate to use with ordered containers, as long as one takes pains to keep the singular values out of the containers.
One example of such a type is IEEE floating point, which is probably the most popular floating-point format these days. It includes the notion of NaN (not a number) values, which have the curious property that any comparison involving a NaN value yields false. Thus, if x is NaN, x<x is false, as one would expect, but so are x<=x, x==x, and x!=x. Needless to say, any order-based algorithm that tries to use NaN values without taking their peculiarities into account is going to become confused.
If we agree to shun NaN values, < behaves completely reasonably on IEEE floating-point numbers. Therefore, if we have a set of IEEE values, it works fine as long as we avoid putting any NaN values into it.
The appropriate requirement to place on < is therefore impossible to check during compilation at all. The best we can do is to say that for set<T> to work correctly, < must behave properly on the values that are placed, or sought, in a set<T> object.
Previous | Table of Contents | Next |