Usage


To start using the Operators library, implement the applicable operator(s) for your class, include "boost/operators.hpp", and derive from one or more of the Operator base classes (they have the same names as the concepts they help implement), which all reside in namespace boost. Note that the inheritance doesn't have to be public; private inheritance works just as well. In this usage section, we look at several examples of using the different concepts, and also take a good look at how arithmetic and relational operators work, both in C++ and conceptually. For the first example of usage, we'll define a class, some_class, with an operator<. We decide that the equivalence relation implied by operator< should be made available through operator==. This can be accomplished by inheriting from boost::equivalent.

 #include <iostream> #include "boost/operators.hpp" class some_class : boost::equivalent<some_class> {   int value_; public:   some_class(int value) : value_(value) {}   bool less_than(const some_class& other) const {     return value_<other.value_;   } }; bool operator<(const some_class& lhs, const some_class& rhs) {   return lhs.less_than(rhs); } int main() {   some_class s1(12);   some_class s2(11);   if (s1==s2)      std::cout << "s1==s2\n";   else     std::cout << "s1!=s2\n"; } 

The operator< is implemented in terms of the member function less_than. The requirement for the equivalent base class is that operator< be present for the class in question. When deriving from equivalent, we pass the derived classthat is, some_classas a template parameter. In main, the operator== that is graciously implemented for us by the Operators library is used. Next, we'll take a look at operator< again, and see what other relations can be expressed in terms of less than.

Supporting Comparison Operators

A relational operator that we commonly implement is less thanthat is, operator<. We do so to support storage in associative containers and sorting. However, it is exceedingly common to supply only that operator, which can be confusing to users of the class. For example, most people know that negating the result of operator< yields operator>=.[1] Less than can also be used to calculate greater than, and so on. So, clients of a class supporting the less than relation have good cause for expecting that the operators that must also (at least implicitly) be supported are also part of the class interface. Alas, if we just add the support for operator< and omit the others, the class isn't as usable as it could, and should, be. Here's a class that's been made compliant with the sorting routines of the Standard Library containers.

[1] Although too many seem to think that it yields operator>!

 class thing {   std::string name_; public:   thing() {}   explicit thing(const std::string& name):name_(name) {}   friend bool operator<(const thing& lhs, const thing& rhs) {     return lhs.name_<rhs.name_;   }  }; 

This class supports sorting, and it can be stored in associative containers, but it may not meet the expectations of the client! For example, if a client needs to know whether thing a is greater than thing b, the client might write code like this:

 // is a greater than b? if (b<a) {} 

Although this is just as correct, it doesn't convey the intent of the code clearly, which is almost as important as the correctness. If the client needs to know whether a is less than or equal to b, he would have to do this:

 // is a less than, or equal to, b? if (!(b<a)) {} 

Again, the code is quite correct, but it will confuse people; the intent is certainly unclear to most casual readers. It becomes even more confusing when introducing the notion of equivalence, which we support (otherwise our class couldn't be stored in associative containers).

 // is a equivalent to b? if (!(a<b) && !(b<a)) {} 

Please note that equivalence is a different relation than equality, a topic which is expanded upon in a later section. All of the aforementioned relational properties are typically expressed differently in C++, namely through the operators that explicitly perform the tests. The preceding examples should look like this (perhaps with the exception of equivalence, but we'll let it pass for now):

 if (a>b) {} if (a<=b) {} if (a==b) {} 

The comments are now redundant, because the code says it all. As is, this code doesn't compile, because the thing class doesn't support operator>, operator<=, or operator==. But, as these operators (except operator==) can always be expressed for types that implement the less_than_comparable concept, the Operators library can help us out. All we need to do is to have thing derive from boost::less_than_comparable, like so:

 class thing : boost::less_than_comparable<thing> { 

This gives you all the operators that can be implemented in terms of operator<, and so, by just specifying a base class, the thing class now works as one would expect it to. As you can see, when deriving thing from a class in the Operators library, we must also pass thing as a template parameter to that base class. This technique is discussed in the following section. Note that operator== is not defined for classes supporting less_than_comparable, but there is another concept that we can use for that one, namely equivalent. Deriving from boost::equivalent adds operator==, but it should be duly noted that operator== is now defined in terms of an equivalence relation, which in turn does not define equality. Equivalence implies a strict weak ordering.[2] Our final version of the class thing looks like this:

[2] If you're wondering what a strict weak ordering is, skip ahead to the next section, but don't forget to return here later!

 class thing :    boost::less_than_comparable<thing>,   boost::equivalent<thing> {   std::string name_; public:   thing() {}   explicit thing(const std::string& name):name_(name) {}   friend bool operator<(const thing& lhs,const thing& rhs) {     return lhs.name_<rhs.name_;   }  }; 

This version only defines a single operator in thing's definition, which keeps the definition concise, and by virtue of the inheritance from less_than_comparable and equivalent, it provides quite an impressive set of useful operators.

 bool operator<(const thing&,const thing&); bool operator>(const thing&,const thing&); bool operator<=(const thing&,const thing&); bool operator>=(const thing&,const thing&); bool operator==(const thing&,const thing&); 

I'm sure you've seen many classes that provide a multitude of operators. Such class definitions can be difficult to read because there are so many operator functions declared/implemented. By inheriting from the concept classes in operators, you provide the same interface but do so more clearly and with much less code. Mentioning these concepts in the class definition makes it obvious for a reader familiar with less_than_comparable and equivalent that the class supports the aforementioned relational operations.

The Barton-Nackman Trick

In the two examples we've seen of inheriting from operator base classes, a strange-looking construct feeds the derived class to its base class. This is a well-known technique that is referred to as either the Barton-Nackmann trick[3] or the Curiously Recurring Template Pattern.[4] The problem that this technique solves is that of a cyclic dependency. Consider implementing a generic class that provides operator== for other classes that define operator<. Incidentally, this is a concept known as equivalent in this library (and mathematics, of course). Now, it is clear that any class utilizing an implementation providing such services needs to know about the enabling classlet's call it equivalent after the concept it helps implement. However, it's just as clear that equivalent needs to know about the class for which it should define operator==! This is a cyclic dependency, and at first glance, there's no easy way out. However, if we make equivalent a class template, and add a template parameter that designates the class for which to define operator==, we have effectively injected the dependent typewhich is the derived classinto the scope of equivalent. This example demonstrates the use of this idea.

[3] "Invented" by John Barton and Lee Nackmann.

[4] "Invented" by James Coplien.

 #include <iostream> template <typename Derived> class equivalent { public:   friend bool operator==(const Derived& lhs,const Derived& rhs) {     return !(lhs<rhs) && !(rhs<lhs);   } }; class some_class : equivalent<some_class> {   int value_; public:   some_class(int value) : value_(value) {}   friend bool operator<(const some_class& lhs,     const some_class& rhs) {     return lhs.value_<rhs.value_;   } }; int main() {   some_class s1(4);   some_class s2(4);   if (s1==s2)     std::cout << "s1==s2\n"; } 

The base classequivalentaccepts a template argument that is the type for which it defines operator==. It implements this operator in a generic fashion by using operator< for the parameterizing type. Then, the class some_class, wishing to utilize the services of equivalent, derives from it and passes itself as equivalent's template parameter. Therefore, the resulting operator== is defined for the type some_class, implemented in terms of some_class's operator<. That's all there is to the Barton-Nackmann trick. This is a simple yet immensely useful pattern, quite beautiful in its elegance.

Strict Weak Ordering

I have already mentioned strict weak orderings twice in this book, and if you're not familiar with what they are, this brief digression should help. A strict weak ordering is a relation between two objects. First, let's get a bit theoretical and then we can make it more concrete. For a function f(a,b) that implements a strict weak ordering, with a and b being two objects of the same type, we say that a and b are equivalent if f(a,b) is false and f(b,a) is false. This means that a does not precede b, and b does not precede a. We can thus consider them to be equivalent. Furthermore, f(a,a) must always yield false[5] and if f(a,b) is true, then f(b,a) must be false.[6] Also, if f(a,b) and f(b,c) is true, then so is f(a,c).[7] Finally, if f(a,b) is false and f(b,a) is false, and if f(b,c) is false and f(c,b) is false, then f(a,c) is false and f(c,a) is false.[8]

[5] This is irreflexivity.

[6] This is antisymmetry.

[7] This is transitivity.

[8] This is transitivity of equivalence.

Applying the preceding to our previous example (with the class thing) can help clarify the theory. The less than comparison for things is implemented in terms of less than for std::string. This, in turn, is a lexicographical comparison. So, given a thing a containing the string "First," a thing b containing the string "Second," and a thing c containing the string "Third," let's assert the earlier definitions and axioms.

 #include <cassert> #include <string> #include "boost/operators.hpp" // Definition of class thing omitted int main() {   thing a("First");   thing b("Second");   thing c("Third");   // assert that a<b<c    assert(a<b && a<c && !(b<a) && b<c && !(c<a) && !(c<b));   // Equivalence   thing x=a;   assert(!(x<a) && !(a<x));   // Irreflexivity   assert(!(a<a));   // Antisymmetry   assert((a<b)==!(b<a));   // Transitivity   assert(a<b && b<c && a<c);   // Transitivity of equivalence   thing y=x;   assert( (!(x<a) && !(a<x)) &&      (!(y<x) && !(x<y)) &&      (!(y<a) && !(a<y)));  } 

Now, all of these asserts hold, because std::string implements a strict weak ordering.[9] Just as operator< should define a strict weak ordering, so should operator>. Later on, we'll look at a very concrete example of what happens when we fail to acknowledge the difference between equivalence (which is required for a strict weak ordering) and equality (which is not).

[9] In fact, std::string defines a total ordering, which is a strict weak ordering with the additional requirement that equivalence and equality are identical.

Avoid Object Bloating

In the previous example, our class derived from two base classes: less_than_comparable<thing> and equivalent<thing>. Depending on your compiler, you may pay a price for this multiple inheritance; thing may be much larger than it needs to be. The standard permits a compiler to use the empty base optimization to make a base class that contains no data members, no virtual functions, and no duplicated base classes, to take zero space in derived class objects, and most modern compilers perform that optimization. Unfortunately, using the Operators library often leads to inheriting from multiple classes and few compilers apply the empty base optimization in that case. To avoid the potential object size bloating, Operators supports a technique known as base class chaining. Every operator class accepts an optional, additional template parameter, from which it derives. By having one concept class derive from another, which derives from another, which derives from another…(you get the idea), the multiple inheritance is eliminated. This alternative is easy to use. Rather than inheriting from several base classes, simply chain the classes together, like so.

 // Before boost::less_than_comparable<thing>,boost::equivalent<thing> // After boost::less_than_comparable<thing,boost::equivalent<thing> > 

This method removes the inheritance from multiple empty base classes, which may not trigger your compiler's empty base optimization, in favor of derivation from a chain of empty base classes, increasing the chance of triggering the empty base optimization and reducing the size of the derived classes. Experiment with your compiler to see what benefits you can gain from this technique. Note that there is a limit to the length of the base class chain that depends upon the compiler. There's also a limit to the length of the chain a human can grok! That means that classes that need to derive from many operator classes may need to group them. Better yet, use the composite concepts already provided by the Operators library.

The difference in size between using base class chaining and multiple inheritance on a popular compiler[10] that doesn't perform the empty base class optimization for multiple inheritance is quite large for my tests. Using base class chaining ensures that the size of types is not negatively affected, whereas with multiple inheritance, the size grows by 8 bytes for a trivial type (admittedly, 8 additional bytes isn't typically a problem for most applications). If the size of the wrapped type is very small, the overhead caused by multiple inheritance is potentially more than is tolerable. Because it is so easy, consider using base class chaining all the time!

[10] I say this both because there's no need for calling names, and because everyone already knows that I'm talking about Microsoft's old compiler (their new one rocks).

Operators and Different Types

Sometimes, an operator involves more than one type. For example, consider a string class that supports concatenation from character arrays through operator+ and operator+=. The Operators library helps here too, by way of the two-argument versions of the operator templates. In the case of the string class, there is probably a conversion constructor available that accepts a char*, but as we shall see, that doesn't solve all of the problems for this class. Here's the string class that we'll use.

 class simple_string { public:   simple_string();   explicit simple_string(const char* s);   simple_string(const simple_string& s);   ~simple_string();   simple_string& operator=(const simple_string& s);   simple_string& operator+=(const simple_string& s);   simple_string& operator+=(const char* s);   friend std::ostream&      operator<<(std::ostream& os,const simple_string& s); }; 

As you can see, we've already added two versions of operator+= for simple_string. One accepts a const simple_string&, and the other accepts a const char*. As is, our class supports usage like this.

 simple_string s1("Hello there"); simple_string s2(", do you like the concatenation support?"); s1+=s2; s1+=" This works, too"; 

Although the preceding works as intended, we still haven't provided the binary operator+, an omission that the class' users definitely won't be pleased with. Note that for our simple_string, we could have opted to enable concatenation by omitting the explicit conversion constructor. However, doing so would involve an extra (unnecessary) copy of the character buffer, and the only savings would be the omission of an operator.

 // This won't compile simple_string s3=s1+s2; simple_string s4=s3+" Why does this class behave so strangely?"; 

Now let's use the Operators library to supply the missing operators for the class. Note that there are actually three missing operators.

 simple_string operator+(const simple_string&,const simple_string&); simple_string operator+(const simple_string& lhs, const char* rhs); simple_string operator+(const char* lhs, const simple_string& rhs); 

When defining operators manually, it's easy to forget one of the overloads for taking one const simple_string& and one const char*. When using the Operators library, you can't forget, because the library is implementing the missing operators for you! What we want for simple_string is the addable concept, so we simply derive simple_string from boost::addable<simple_string>.

 class simple_string : boost::addable<simple_string> { 

In this case, however, we also want the operators that allow mixing simple_strings and const char*s. To do this, we must specify two typesthe result type, simple_string, and the second argument type, const char*. We'll utilize base class chaining to avoid increasing the size of the class.

 class simple_string :        boost::addable<simple_string,     boost::addable2<simple_string,const char*> > { 

This is all that's needed for supporting the full set of operators that we aimed for! As you can see, we used a different operator class: addable2. If you're using a compiler that supports partial template specialization, you don't have to qualify the name; use addable instead of addable2. There are also versions of the classes with the suffix "1" provided for symmetry. It may increase the readability to always be explicit about the number of arguments, which gives us the following derivation for simple_string.

 class simple_string :        boost::addable1<simple_string,     boost::addable2<simple_string,const char*> > { 

Choose between them according to taste, and if your compiler supports partial template specialization, the simplest choice is to omit the suffixes altogether.

 class simple_string :        boost::addable<simple_string,     boost::addable<simple_string,const char*> > { 

The Difference Between Equality and Equivalence

When defining relational operators for classes, it's important to make the distinction between equality and equivalence. An equivalence relation is required in order to use the associative containers, and it defines a strict weak ordering through the concept LessThanComparable.[11] This relation makes the least assumptions, and poses as few requirements as possible, for types that are to be used with the Standard Library containers. However, the difference between equality and equivalence can sometimes be confusing, and it is important to understand the difference. When a class supports the concept LessThanComparable, it typically also supports the notion of equivalence. If two elements are compared, and neither is less than the other, we can consider them to be equivalent. However, equivalence doesn't necessarily mean equal. For example, it may be reasonable to omit certain characteristics from a less than relation, but consider them for equality.[12] To illustrate this, let's look at a class, animal, which supports both an equivalence relation and an equality relation.

[11] Capitalized concepts like LessThanComparable come straight from the C++ Standard. All of the concepts in Boost.Operators use lowercase names.

[12] Which implies a strict weak ordering, but not a total ordering.

 class animal : boost::less_than_comparable<animal,  boost::equality_comparable<animal> > {   std::string name_;   int age_; public:   animal(const std::string& name,int age)     :name_(name),age_(age) {}   void print() const {     std::cout << name_ << " with the age " << age_ << '\n';   }      friend bool operator<(const animal& lhs, const animal& rhs) {     return lhs.name_<rhs.name_;   }   friend bool operator==(const animal& lhs, const animal& rhs) {     return lhs.name_==rhs.name_ && lhs.age_==rhs.age_;   } }; 

Notice the difference between the implementation of operator< and that of operator==. Only the animal's name is part of the less than relation, whereas comparison of both the name and the age comprise the equality test. There is nothing wrong with this approach, but it can have interesting ramifications. Let's now put this class into action by storing some elements of the class in a std::set. Just like other associative containers, set only relies on the concept LessThanComparable. In the sample code that follows, we create four animals that are all different, and then try to insert them into a set, all while pretending we don't know that there is a difference between equality and equivalence.

 #include <iostream> #include <string> #include <set> #include <algorithm> #include "boost/operators.hpp" #include "boost/bind.hpp" int main() {   animal a1("Monkey", 3);   animal a2("Bear", 8);   animal a3("Turtle", 56);   animal a4("Monkey", 5);   std::set<animal> s;   s.insert(a1);   s.insert(a2);   s.insert(a3);   s.insert(a4);   std::cout << "Number of animals: " << s.size() << '\n';   std::for_each(s.begin(),s.end(),boost::bind(&animal::print,_1));   std::cout << '\n';   std::set<animal>::iterator it(s.find(animal("Monkey",200)));   if (it!=s.end()) {     std::cout << "Amazingly, there's a 200 year old monkey "       "in this set!\n";     it->print();   }   it=std::find(s.begin(),s.end(),animal("Monkey",200));   if (it==s.end()) {     std::cout << "Of course there's no 200 year old monkey "       "in this set!\n";   } } 

Running the program produces the following, utterly nonsensical, output.

 Number of animals: 3 Bear with the age 8 Monkey with the age 3 Turtle with the age 56 Amazingly, there's a 200 year old monkey in this set! Monkey with the age 3 Of course there's no 200 year old monkey in this set! 

The problem is not the age of the monkeyit very seldom isbut the failure to distinguish between two related concepts. First, when the four animals (a1, a2, a3, a4) are inserted into the set, the second monkey, a4, is actually not inserted at all, because a1 and a4 are equivalent. The reason is that std::set uses the expression !(a1<a4) && !(a4<a1) to decide whether there is already a matching element. Because the result of that expression is true (our operator< doesn't include the age), the insertion fails.[13] Then, when we ask the set to search for a 200 year old monkey using find, it supposedly locates such a beast. Again, this is because of the equivalence relation for animal, which relies on animal's operator< and thus, doesn't care about age. We use find again to locate the monkey in the set (a1), but then, to decide whether it matches, we call on operator== and find that the monkeys don't match. It's not hard to understand the difference between equality and equivalence when looking at these monkeys, but it is imperative to know which one is applicable for a given context.

[13] A set, by definition, does not contain duplicates.

Arithmetic Types

The Operators library is especially useful when defining arithmetic types. There are many operators that must be defined for an arithmetic type, and doing it manually is a daunting, tedious task, with plenty of opportunity for errors or omissions. The concepts that are defined by the Operators library make it easy to define only the bare minimum of operators for a class, and have the rest supplied automagically. Consider a class that is to support addition and subtraction. Assume that this class uses a built-in type for its implementation. Now add the appropriate operators and be sure that they work with not only instances of that class, but also with the built-in types that are convertible to the implementation type. You'll need to provide 12 different addition and subtraction operators. The easier (and safer!) approach, of course, is to use the two-argument form of the addable and subtractable classes. Now suppose you need to add the set of relational operators, too. You could probably add the 10 operators needed yourself, but by now you know that the easiest thing is to use less_than_comparable and equality_comparable. Having done so, you'd have 22 operators for the cost of 6. However, you might also note that these concepts are common for value type classes. Indeed, instead of using those four classes, you could just use additive and totally_ordered.

We'll start by deriving from all four of the concept classes: addable, subtractable, less_than_comparable, and equality_comparable. The class, limited_type, just wraps a built-in type and forwards any operation to that type. It limits the number of available operations, providing just the relational operators and those for addition and subtraction.

 #include "boost/operators.hpp" template <typename T> class limited_type :    boost::addable<limited_type<T>,     boost::addable<limited_type<T>,T,       boost::subtractable<limited_type<T>,         boost::subtractable<limited_type<T>,T,           boost::less_than_comparable<limited_type<T>,             boost::less_than_comparable<limited_type<T>,T,               boost::equality_comparable<limited_type<T>,                 boost::equality_comparable<limited_type<T>,T > > > > > > > > {   T t_; public:   limited_type():t_() {}   limited_type(T t):t_(t) {}   T get() {     return t_;   }   // For less_than_comparable   friend bool operator<(       const limited_type<T>& lhs,        const limited_type<T>& rhs) {     return lhs.t_<rhs.t_;   }   // For equality_comparable   friend bool operator==(       const limited_type<T>& lhs,        const limited_type<T>& rhs) {     return lhs.t_==rhs.t_;   }   // For addable   limited_type<T>& operator+=(const limited_type<T>& other) {     t_+=other.t_;     return *this;   }   // For subtractable   limited_type<T>& operator-=(const limited_type<T>& other) {     t_-=other.t_;     return *this;   } }; 

This is a good example of how easy the implementation becomes when using the Operators library. Implementing the few operators that must be implemented to get support for the full set of operators is typically not that hard, and the class becomes much more understandable and maintainable than would otherwise have been the case. (Even if implementing those operators is hard, you can concentrate on getting just those few right.) The only potential problem with the class is the derivation from eight different operator classes which, when using base class chaining, is not as readable as one would like. We can greatly simplify our class by using composite concepts instead.

 template <typename T> class limited_type :    boost::additive<limited_type<T>,      boost::additive<limited_type<T>,T,        boost::totally_ordered<limited_type<T>,          boost::totally_ordered<limited_type<T>,T > > > >  { 

This is much nicer, and it does save some typing, too.

Use Operators Only When Operators Should Be Used

It may seem really obvious that operators should be used only when appropriate, but for some reason there is a certain "coolness factor" about operators that seems to tempt some people to add them even when their semantics are unclear. There are many scenarios requiring operators, such as when there is a relation between instances of a type, or when creating an arithmetic type. But there are also less clear-cut cases, where one needs to consider the expectations of clients of the class, and where perceived ambiguity might make a member function a better choice.

Operators have been plied into unusual service over the years. Concatenating strings with addition operators and I/O with shift operators are two common examples where the operators do not necessarily have a mathematical meaning, but have been used for other semantic purposes. Some have questioned the use of the subscript operator for accessing elements in a std::map. (Others, of course, think it's perfectly natural. And they are right.) Sometimes, using operators for tasks other than their role with built-in types makes sense. Other times, it can go horribly wrong, causing confusion and ambiguities. When you choose to overload operators with meanings that deviate from those of the built-in types, you must do so carefully. You must ensure that the meaning is obvious and that the precedence is correct. That was the reason for choosing the shift operators for I/O in the IOStreams library. The operators clearly suggested moving something one direction or the other and the precedence of the shift operators put them lower than most others. If you create a class representing a car, some might find operator-= convenient. However, what might that operator mean to clients? Some might think it was used to account for gasoline used while driving. Others might think that it was used to account for depreciation of the car's value (an accountant, of course). Adding that operator is wrongheaded because it doesn't have a clear purpose, whereas a member function can name the operation providing clarity. Don't add operators just because it makes for "cool" coding. Add them because it makes sense, be sure to add all the operators that apply, and be sure to use the Boost.Operators library!

Understanding How It Works

We'll now take a look at how this library works, to further your understanding of how to use it properly. For Boost.Operators, it's not a hard thing to do. Let's see how to implement support for less_than_comparable. You need to know the type for which you'll add support, and you need to augment that type with operators that use one or more operators supplied for that type. less_than_comparable requires that we provide operator<, operator>, operator<=, and operator>=. Of these, by now, you know how to implement operator>, operator<=, and operator>= in terms of operator<. Here's how one might implement it.

 template <class T> class less_than1 { public:   friend bool operator>(const T& lhs,const T& rhs)  {      return rhs<lhs;    }   friend bool operator<=(const T& lhs,const T& rhs) {      return !(rhs<lhs);    }   friend bool operator>=(const T& lhs,const T& rhs) {      return !(lhs<rhs);    } }; 

For operator>, you just need to switch the order of the arguments. For operator<=, observe that a<=b means that b is not less than a. Thus, the implementation is to call operator< with the arguments in reverse order and negate the result. For operator>=, there's the similar observation that a>=b also means that a is not less than b. Thus, the implementation just negates the result of calling operator<. This is a working example: You could use it directly and it would do the right thing. However, it would be nice to also have a version that supports comparisons between T and compatible types, which is simply a case of adding more overloads. For symmetry, you need to allow either type to be on the left side of the operation. (This is easy to forget when adding operators manually; one tends to only see clearly the fact that the right side must accept the other type. Of course, your two-type version of less_than wouldn't make such silly mistakes, right?)

 template <class T,class U> class less_than2 { public:   friend bool operator<=(const T& lhs,const U& rhs) {      return !(lhs>rhs);    }   friend bool operator>=(const T& lhs,const U& rhs) {      return !(lhs<rhs);    }   friend bool operator>(const U& lhs,const T& rhs) {     return rhs<lhs;    }   friend bool operator<(const U& lhs,const T& rhs)  {      return rhs>lhs;    }   friend bool operator<=(const U& lhs,const T& rhs) {      return !(rhs<lhs);    }   friend bool operator>=(const U& lhs,const T& rhs) {      return !(rhs>lhs);    } }; 

There it is! Two fully functioning less_than classes. Of course, to match the functionality of less_than_comparable in the Operators library, we must somehow get rid of the suffix stating how many types are used. What we really want is one version, or at least one name. If you are working with a compiler that supports partial template specialization, you're in luck, because it's basically a three-liner to make this happen. But, there are still a number of programmers who don't have that luxury, so we'll do it the hard way, and avoid partial specialization altogether. First, we know that we need something called less_than, which is to be a template accepting one or two argument types. We also know that the second type should be optional, which we can accomplish by adding a default type that we know users won't pass to the template.

 struct dummy {}; template <typename T,typename U=dummy> class less_than {}; 

We need some mechanism for selecting the correct version of less_than (less_than1 or less_than2); we can do this without partial template specialization by using an auxiliary class that is parameterized on one type, with a nested template struct that accepts an additional type. Then, using full specialization, we can make sure that whenever the type U is dummy, less_than1 is selected.

 template <typename T> struct selector {   template <typename U> struct type {     typedef less_than_2<U,T> value;   }; }; 

The preceding version creates a type definition called value, which is a correct instantiation of the less_than2 template that we've created.

 template<> struct selector<dummy> {   template <typename U> struct type {     typedef less_than1<U> value;   }; }; 

The fully specialized selector creates a typedef for the other version, less_than1. To make it easier for the compiler, we'll create another auxiliary class with the sole responsibility of collecting the correct type and storing it in the suitably named typedef type.

 template <typename T,typename U> struct select_implementation {   typedef typename selector<U>::template type<T>::value type; }; 

The syntax is not so pleasing to the eye, because of the nested parameterized struct in the selector class, but as clients of this class don't have to read this part of the code, that's really not a big issue. Now that we have all the ingredients that we need to select a correct implementation, we finalize the class by deriving less_than from select_implementation<T,U>::type, which evaluates to either less_than1 or less_than2, depending on whether the user has supplied one or two types to our class.

 template <typename T,typename U=dummy> class less_than :    select_implementation<T,U>::type {}; 

That's it! We now have a fully working version of less_than, which users can use in the easiest possible way due to the extra effort we spent in adding a mechanism for detecting and selecting the correct version of the implementation. We also know exactly how operator< can be used to create the remaining operators that are applicable for any type that is less_than_comparable. Doing the same for the other operators is just a matter of being meticulous and understanding how different operators work together to form new concepts.

The Things That Remain

We haven't yet spoken about the remaining part of the Operators library, the iterator helpers. I won't show example code for those, because you'll mainly want to use them when defining iterator types, and that needs additional explanation that does not fit in this chapter or in this book. However, I mention them here because if you are defining iterator types without the help of Boost.Iterators, you most definitely want to use these helpers. The dereference operators help define the correct operators regardless of whether you are using a proxy class. They are also useful when defining smart pointers, which typically also require defining both operator-> and operator*. The iterator helpers group together concepts that are required for the different types of iterators. For example, a random access iterator needs to be bidirectional_iterable, totally_ordered, additive, and indexable. When defining new iterator types, which should preferably be done with the help of the Boost.Iterator library, the Operators library can help.



    Beyond the C++ Standard Library(c) An Introduction to Boost
    Beyond the C++ Standard Library: An Introduction to Boost
    ISBN: 0321133544
    EAN: 2147483647
    Year: 2006
    Pages: 125

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net