Changing element types in inheritance

Given a base class Base

that has two derived classes, DerA

and DerB

, can derived classes have a member variable that is used in a member function Base

, but for each type of class?

class Base {
    * a // Declare a as *something*, so it can be used by "doWork"

    template <typedef T>
    void doWork(T b) { // Add another value to "a" which is of its same type
        a += b; // For example; an operation that works on "a", no matter what numeric type it is
    }
}

class DerA : public Base {
    // Make "a" an int
}

class DerB : public Base {
    // Make "a" a float
}

      

In practice, it a

will be a base structure, and DerA

they DerB

will have derived versions of the base structure (derived classes will have a derived form of a structure specific to their purpose, but everyone has to do a simple operation on a

, so it seems pointless to copy / paste this simple function for each derivative when I can just use the template function). I would just a

type struct as the base type, but then I lose access to the various specialized functions and variables that each derived struct has (if I understand inheritance correctly).

I apologize if this question is repeated, but I do not know what this quality will be called, so Googling has proven fruitless.

+3


source to share


3 answers


What you might need is CRTP .

template<class D>
struct Base {
  D* self() { return static_cast<D*>(this); }
  D const* self() const { return static_cast<D*>(this); }
  template<class T>
  void doWork(T b) {
    self()->a += b;
  }
};
struct DerA : public Base<DerA> {
  int a;
};
struct DerB : public Base<DerB> {
  double a;
};

      

Here we are passing the derived type to our base class. In the base class, you can use self()->

to access derived type fields. This basically allows full access to the derived type, allowing us to share code in the base class.

Please note that you cannot transfer to DerA

and DerB

around like Base

this. If you want that, you need a method virtual

doWork

, and methods virtual

template

don't exist.

CRTP stands for Curiously Repeating Pattern Pattern, which I think is named because it's weird, it involves type repetition, and it keeps showing up in strange corners as useful.


Erase type probably won't work either, since you want to send a type erase from two different places in your code base (double dispatch problem: you need a centralized list of types supported to work with a Cartesian type).



To expand this, to support a+=b

where both a

and b

are arbitrary types, you would have to expand all types twice, including types that will never be mutually visible in the same place in the compilation unit. It's impossible.


If you want a generic base and there are only a few types that you transition to doWork

, here's how you do it:

struct Base {
  virtual void doWork( double ) = 0;
  virtual void doWork( int ) = 0;
  virtual void doWork( long long ) = 0;
};

template<class D>
struct Base_helper:Base {
  D* self() { return static_cast<D*>(this); }
  D const* self() const { return static_cast<D*>(this); }
  template<class T>
  void doWork_impl(T b) {
    self()->a += b;
  }
  void doWork( double x ) override { doWork_impl(x); };
  void doWork( int x ) override { doWork_impl(x); };
  void doWork( long long x ) override { doWork_impl(x); };
};
struct DerA : public Base_helper<DerA> {
  int a;
};
struct DerB : public Base_helper<DerB> {
  double a;
};

      

note that each version doWork

must be valid to call each of the Der

s as it Base_helper

creates all of them.

If the type of the type passed to doWork

is unbounded, still the types are Der

limited, you can do something like above just backwards. However, this becomes inconvenient. It is best to use a solution like boost::variant

.

+3


source


I think you want to achieve something like this:

template<typedef T>
class Base {
    T a;
    void doWork(T b) { // Add another value to "a" which is of its same type
        a += b; // For example; an operation that works on "a", no matter what numeric type it is
    }
}

class DerA : public Base<int> {
}

class DerB : public Base<float> {
}

      



Or you can dump the DerA and DerB classes entirely and use typedefs:

typedef Base<int> DerA;
typedef Base<float> DerB;

      

+2


source


This can be easily solved with a CRTP-like pattern:

template<class D> // Base class is templated
class Base {
public:
    D a;
    void doWork(D b) {
        a += b;
    }
};

class DerA : public Base<int> {};

class DerB : public Base<float> {};

      

Edit: in case you only need one common base ( Base<int>

- a completely different type from Base<float>

), you can use the interface class and inherit Base from it.

+2


source







All Articles