Expanding the interface

Let's consider several examples where CRTP is used to delegate behavior from the base class to the derived one. 

The first example is a very simple one—for any class that provides operator==(), we want to generate operator!=() automatically as the inverse of the former:

template <typename D> struct not_equal {
bool operator!=(const D& rhs) const {
return !static_cast<const D*>(this)->operator==(rhs);
}
};

class C : public not_equal<C> {
int i_;
public:
C(int i) : i_(i) {}
bool operator==(const C& rhs) const { return i_ == rhs.i_; }
};

Any class that inherits from not_equal in this manner automatically acquires the not equal operator that is guaranteed to match the provided operator, equal. An observant reader might point out that the way we have declared the operators == and != here is, well, strange. Aren't they supposed to be non-member functions? Indeed, they usually are. Nothing in the standard requires them to be, and the preceding code is technically valid. The reason binary operators such as ==, +, and so on are usually declared as non-member functions has to do with implicit conversions—if we have a comparison, x == y, and the intended operator== is a member function, it has to be a member function of the x object. Not any object implicitly convertible to the type of x, but x itself—it's a member function call on x. In contrast, the y object has to be implicitly convertible to the type of the argument of that member operator==, usually, the same type as x. To restore the symmetry and allow implicit conversions (if any are provided) on both the left-and right-hand side of the == sign, we have to declare operator== as a non-member function. Usually, such a function needs to have access to the private data members of the class, as in the example previously, so it has to be declared a friend. Putting all of the above together, we arrive at this alternative implementation:

template <typename D> struct not_equal {
friend bool operator!=(const D& lhs, const D& rhs) {
return !(lhs == rhs);
}
};

class C : public not_equal<C> {
int i_;
public:
C(int i) : i_(i) {}
friend bool operator==(const C& lhs, const C& rhs) {
return lhs.i_ == rhs.i_;
}
};

Note that this implementation of not_equal will work fine even if the derived class provides the operator== as a member function (with the preceding clarification of the difference in implicit conversions in mind).

There is a significant difference between this use of CRTP and the one we saw earlier—the object that is going to be used in the program is of type C, and it will never be accessed through a pointer to not_equal<C>. The latter isn't a complete interface for anything but is really an implementation that makes use of the interface provided by the derived class. 

A similar but slightly more complete example is an object registry. It may be desirable, often for debugging purposes, to know how many objects of a certain type are currently in existence, and perhaps even to maintain a list of such objects. We definitely do not want to instrument every class with the registry mechanism, so we want to move it to the base class. But, now we have a problem—if we have two derived classes, C and D, both inherit from the same base class, B, and the count of instances of B will be the total for both C and D. The problem is not that the base class can't determine what the real type of the derived class is—it can, if we're willing to pay the cost of runtime polymorphism. The problem is that the base class has only one counter (or however many are coded in the class), while the number of different derived classes is unlimited. We could implement a very complex, expensive, and non-portable solution that uses Run-Time Type Information (RTTI), such as typeid, to determine the class name and maintain a map of names and counters. But, what we really need is one counter per derived type, and the only way to do it is to make the base class aware of the derived class type at compile time. This brings us back to CRTP:

template <typename D> class registry {
public:
static size_t count;
static D* head;
D* prev;
D* next;
protected:
registry() {
++count;
prev = nullptr;
next = head;
head = static_cast<D*>(this);
if (next) next->prev = head;
}
registry(const registry&) {
++count;
prev = nullptr;
next = head;
head = static_cast<D*>(this);
if (next) next->prev = head;
}
~registry() {
--count;
if (prev) prev->next = next;
if (next) next->prev = prev;
if (head == this) head = next;
}

};
template <typename D> size_t registry<D>::count(0);
template <typename D> D* registry<D>::head(nullptr);

We have declared the constructor, and the destructor protected because we don't want any registry objects created, except by the derived classes. It is also important to not forget the copy constructor, otherwise the default one is generated by the compiler, and it does not increment the counter or update the list (but the destructor does, so the counter will go negative and overflow). For each derived class, D, the base class is registry<D>, which is a separate type with its own static data members, count and head (the latter is the head of the list of currently active objects). Any type that needs to maintain the runtime registry of active objects now only needs to inherit from registry:

class C : public registry<C> {
int i_;
public:
C(int i) : i_(i) {}
};

A similar example, where the base class needs to know the type of the derived class and use it to declare its own members, can be found in Chapter 9, Named Arguments and Method Chaining.

Another scenario where it is often necessary to delegate behavior to derived classes is the problem of visitation. Visitors, in a general sense, are objects that are invoked to process a collection of data objects and execute a function on each one in turn. Often, there are hierarchies of visitors, where the derived classes customize or alter some aspect of the behavior of the base classes. While the most common implementation of visitors uses dynamic polymorphism and virtual function calls, a static visitor offers the same sort of performance benefits we saw earlier. Visitors are not usually invoked polymorphically; you create the visitor you want and run it. The base visitor class, however, does call the member functions that may be dispatched, at compile time, to the derived classes if they have the right overrides. Consider this generic visitor for a collection of animals:

struct Animal {
public:
enum Type { CAT, DOG, RAT };
Animal(Type t, const char* n) : type(t), name(n) {}
const Type type;
const char* const name;
};

template <typename D> class GenericVisitor {
public:
template <typename it> void visit(it from, it to) {
for (it i = from; i != to; ++i) {
this->visit(*i);
}
}
private:
D& derived() { return *static_cast<D*>(this); }
void visit(const Animal& animal) {
switch (animal.type) {
case Animal::CAT:
derived().visit_cat(animal);
break;
case Animal::DOG:
derived().visit_dog(animal);
break;
case Animal::RAT:
derived().visit_rat(animal);
break;
}
}
void visit_cat(const Animal& animal) {
std::cout << "Feed the cat " << animal.name << std::endl;
}
void visit_dog(const Animal& animal) {
std::cout << "Wash the dog " << animal.name << std::endl;
}
void visit_rat(const Animal& animal) {
std::cout << "Eeek!" << std::endl;
}
friend D;
GenericVisitor() {}
};

Note that the main visitation method is a template member function (a template within a template!), and it accepts any kind of iterator that can iterate over a sequence of Animal objects. Also, by declaring a private default constructor at the bottom of the class, we are protecting ourselves from making a mistake where the derived class incorrectly specifies its own type for the inheritance. Now, we can start creating some visitors. The default visitor simply accepts the default actions provided by the generic visitor:

class DefaultVisitor : public GenericVisitor<DefaultVisitor> {
};

We can visit any sequence of Animal objects, for example, a vector:

std::vector<Animal> animals {{Animal::CAT, "Fluffy"}, 
{Animal::DOG, "Fido"},
{Animal::RAT, "Stinky"}};
DefaultVisitor().visit(animals.begin(), animals.end());

The visitation yields the expected result:

But, we don't have to constrain ourselves to the default actions—we can override the visitation actions for one or more animal types:

class TrainerVisitor : public GenericVisitor<TrainerVisitor> {
friend class GenericVisitor<TrainerVisitor>;
void visit_dog(const Animal& animal) {
std::cout << "Train the dog " << animal.name << std::endl;
}
};

class FelineVisitor : public GenericVisitor<FelineVisitor> {
friend class GenericVisitor<FelineVisitor>;
void visit_cat(const Animal& animal) {
std::cout << "Hiss at the cat " << animal.name << std::endl;
}
void visit_dog(const Animal& animal) {
std::cout << "Hiss at the dog " << animal.name << std::endl;
}
void visit_rat(const Animal& animal) {
std::cout << "Eat the rat " << animal.name << std::endl;
}
};

When a dog trainer chooses to visit our animals, we use TrainerVisitor:

Finally, a visiting cat would have a set of actions all of its own:

We will learn a lot more about different kinds of Visitors later, in Chapter 18, The Visitor Pattern and Multiple Dispatch.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.100.202