boost::addable { The Difference Between Equality and Equivalence When defining relational operators for classes, it's important to make the distinction between equality and equivalence.
Trang 1boost::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_;
Trang 2}
};
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()) {
Trang 3std::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
Trang 4that 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
Trang 5friend 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
Trang 6Use Operators Only When Operators Should Be Used