Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should members variables used by the CRTP base type be in the derived type?

Tags:

c++

crtp

I've been learning about CRTP (Curiously Recurring Template Pattern) today and believe I understand it well enough.

However, in the examples I've seen the state is stored in the derived type, even though the base type relies upon their presence. To me, this seems illogical and the member variables should be in the base type since its functionality relies upon them.

Here's a simple example of what I'm talking about:

template <typename DerivedType>
class Base{
public:
    int calculate() {
        return static_cast<DerivedType&>(*this).x + static_cast<DerivedType&>(*this).y;
    }
};

class Derived : Base<Derived>{
public:
    int x; // ignore the fact that these aren't initialised for simplicity
    int y;
};

My question is:

Am I correct in thinking that the members x and y would be better off in the base type? If not, why?

like image 290
OMGtechy Avatar asked Jul 30 '14 23:07

OMGtechy


2 Answers

Short answer

It depends on what you need to do, but if different derived classes provide different types of x, y, then these cannot be in the base class.

Long answer

The most common use of inheritance is that a base class includes whatever is common in a number of (more than one) derived classes. This common part is written only once and reused by each derived class, leading to shorter, cleaner code, easier maintenance etc. The common part is calculate() in your case.

Now, wherever this common code needs to access information that is specialized per derived class, this information needs to be accessed through a common interface. In your example, this information is members x, y that may be of different types for each derived class. Or, it could be member functions x(), y(). Such functions could take different types (but same number) of arguments and have different return types per derived class.

Either way, it is the job of the derived class to provide a common interface to heterogeneous information. For CRTP/static polymorphism, this common interface is merely the name of the member and the number of arguments, in the case of member functions. For dynamic polymorphism, the relevant mechanism is virtual functions and the common interface includes the entire signature of the function.

It doesn't matter where the data are actually stored; this depends on many things. It may well be the case that the data are stored in the base class after all, however they are still accessed through member functions in the derived classes.

An example is a tuple implementation where a base class implements all common functionality among different kinds of tuples, whereas a number of tuple views are derived from this base to model operations like flipping the order of tuple elements, concatenating or "zipping" tuples together etc. Note that all such views are lazy, similarly to the way an std::reverse_iterator lets you traverse a sequence in reverse order without actually manipulating the data in advance.

In this case, member function at() of the base class provides random access to a tuple element. This calls call_at() of the derived class, which in turn accesses data that are actually stored in the base class. So, each derived class only knows where each element is to be found; using this information, the base class implements all the remaining functionality (for instance, an operator[] that yields a new tuple where each element is the result of applying operator[] to the respective element of the original tuple).

D's template mixins provide a much more convenient and less verbose alternative to CRTP; almost as convenient as macros. Your static_cast<DerivedType&>(*this).x and my der().x would be just x in this case. Plus, you wouldn't need DerivedType within Base at all.

like image 125
iavr Avatar answered Nov 07 '22 15:11

iavr


I think it is better to assume that the derived class has two member functions x() and y(). You can change your implementation of Base::calculate() to use these functions instead of using the member variables.

Then, the derived class has a lot more freedom in the kinds of data it holds.

like image 1
R Sahu Avatar answered Nov 07 '22 16:11

R Sahu