nass nass - 2 months ago 13
C++ Question

c++ decorator pattern, static polymorphism with templates and registering callback methods

I am attempting to use static polymorphism to create a decorator pattern.
As to why I do not use dynamic polymorphism, please see this QA. Basically, I could not

dynamic_cast
to each decorator so as to access some specific functionality present only in the decorators (and not in the base class A).

With static polymorphism this problem has been overcome, but now I cannot register all the
et()
methods from the decorators back to the base class A (as callbacks or otherwise), thus when
A::et()
gets called, only
A::et()
and
Z::et()
get executed. I want all of
A,X,Y,Z ::et()
to be executed (the order for X,Y,Z does not matter).

How can I do that using the following structure?
I can see in wikipedia that CRTP should allow you to access member of a derived class using
static_cast
, but how do you approach the problem when there are multiple derived template classes?

If this is not possible with static polymorphism but it is possible with dynamic polymorphism could you reply to the other question?

struct I {
virtual void et() = 0;
};

class A : public I {
public:
A() {
cout << "A::ctor " ;
decList.clear();
}
void regDecorator(I * decorator)
{
if (decorator) {
cout << "reg= " << decorator << " ";
decList.push_back(decorator);
}
else
cout << "dec is null!" <<endl;
}
virtual void et()
{
cout << "A::et ";
cout << "declist size= " << decList.size() << endl;

list<I*>::iterator it;
for( it=decList.begin(); it != decList.end(); it++ )
static_cast<I *>(*it)->et();
}

std::list<I*> decList; //FIXME
};

template<typename Base>
class X: public Base {
public:
X(){
cout << "X::ctor ";
Base::regDecorator(this);
}

virtual void et(){
cout << "X::et" <<endl;
}
};

template<typename Base>
class Y: public Base {//public D {
public:
Y(){
cout << "Y::ctor ";
Base::regDecorator(this);
}

void et(){
cout << "Y::et" <<endl;
}
};

template<typename Base>
class Z: public Base {//public D {
public:
Z() {
cout << "Z::ctor ";
Base::regDecorator(this);
}
void et(){
cout << "Z::et" <<endl;
}
};

int main(void) {
Z<Y<X<A> > > mlka;
cout << endl;
mlka.et();
return 0;
}


This structure is to be used as a reference for data acquisition from a set of sensors. class A is the base class and contains common functionality of all the sensors. This includes:

- data container (f.e. `boost::circular_buffer`) to hold an amount of timestamped sample data acquired from the sensor.
- a Timer used to measure some timed quantities related to the sensors.
- other common data and calculation methods (fe. `calculateMean()`, `calculateStdDeviation()`)


In fact the
A::timer
will call
A::et()
on completion in order to perform some statistical calculations on the sampled data.

Similarly, X,Y,Z are types of sensor objects each with responsibility to extract different type of information from the sampled data. and
X,Y,Z::et()
perform a different type of statistical calculation on the data. The aim is perform this calculation as soon as the
A::Timer
waiting time elapses. This is why I want to have access to all of X,Y,Z::et() from A::et(). Is it possible without affecting the static polymorphism shown in the example?

Thank you

Answer

You started using mixins, so use them to the end.
It follows a minimal, working example:

#include<iostream>

struct I {
    virtual void et() = 0;
};

template<typename... T>
struct S: I, private T... {
    S(): T{}... {}

    void et() override {
        int arr[] = { (T::et(), 0)..., 0 };
        (void)arr;
        std::cout << "S" << std::endl;
    }
};

struct A {
    void et() {
        std::cout << "A" << std::endl;
    }
};

struct B {
    void et() {
        std::cout << "B" << std::endl;
    }
};

int main() {
    I *ptr = new S<A,B>{};
    ptr->et();
    delete ptr;
}

As in the original code, there is an interface I that offers the virtual methods to be called.
S implements that interface and erases a bunch of types passed as a parameter pack.
Whenever you invoke et on a specialization of S, it invokes the same method on each type used to specialize it.

I guess the example is quite clear and can serve as a good base for the final code.
If I've understood correctly the real problem, this could be a suitable design for your classes.

EDIT

I'm trying to reply to some comments to this answer that ask for more details.

A specialization of S is all the (sub)objects with which it is built.
In the example above, S<A, B> is both an A and a B.
This means that S can extend one or more classes to provide common data and can be used as in the following example to push around those data and the other subobjects:

#include<iostream>

struct I {
    virtual void et() = 0;
};

struct Data {
    int foo;
    double bar;
};

template<typename... T>
struct S: I, Data, private T... {
    S(): Data{}, T{}... {}

    void et() override {
        int arr[] = { (T::et(*this), 0)..., 0 };
        (void)arr;
        std::cout << "S" << std::endl;
    }
};

struct A {
    void et(Data &) {
        std::cout << "A" << std::endl;
    }
};

struct B {
    void et(A &) {
        std::cout << "B" << std::endl;
    }
};

int main() {
    I *ptr = new S<A,B>{};
    ptr->et();
    delete ptr;
}
Comments