HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppModerate

Compile time decorator pattern in C++ with templates

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
compilewithtimetemplatesdecoratorpattern

Problem

I'm doing some high energy physics modelling in C++. I have written code that implements class that score interactions of particles with detector material.

Here is a base class:

class XYZScorer: public virtual AbstractScorer{
public:

    XYZScorer(double material_rad_len=-1):
        AbstractScorer(material_rad_len){;}

    virtual void RegisterHit(const XYZHit hit)=0;

};


Function RegisterHit registers single interaction between particle and detector. There are many scorers in this program.

Since modeling experimental data is quite hard, I need to be able to add some features to these scorers and I'll need to be able to easily switch these features on and off. I decided to implement these features using a decorator pattern. Here is decorator base class.

template 
class ScorerDecorator: public virtual XYZScorer{

protected:
    Scorer scorer;
public:
    ScorerDecorator(Scorer scorer): XYZScorer(scorer.material_rad_len), scorer(scorer){;}
    virtual ~ScorerDecorator(){}

    virtual void SetEvent(size_t new_incident){
        scorer.SetEvent(new_incident);
    }

    virtual void CloseAfterRunEnd(){
        scorer.CloseAfterRunEnd();
    }
};


Here are two implementations of these decorators:

```
template
class FixZToFirstInteractionScorer: public virtual ScorerDecorator{
private:
bool fixed_in_this_event=false;
double position_of_first_intraction=0;
public:

FixZToFirstInteractionScorer(Scorer scorer): ScorerDecorator(scorer){}
virtual ~FixZToFirstInteractionScorer(){}

virtual void SetEvent(size_t new_incident){
fixed_in_this_event=false;
ScorerDecorator::SetEvent(new_incident);
}

virtual void RegisterHit(const XYZHit hit){
if(!fixed_in_this_event){
fixed_in_this_event=true;
position_of_first_intraction=hit.z;
}
XYZHit fixed_coords = hit;
fixed_coords.z-=position_of_first_intraction;
this->scorer.RegisterHit(fixed_coords);

Solution

I might have a number of minor stylistic comments but this is not important. What is important is the desired function decorate():

template 
using result = typename std::result_of ::type;

template  class E, template  class... D>
struct decorate_impl
{
    template (A)>>
    E operator()(A&& a) const
    {
        return E(decorate_impl()(std::forward(a)));
    }
};

template  class E>
struct decorate_impl 
{
    template ::type>
    E operator()(A&& a) const { return E(std::forward(a)); }
};

template  class E, template  class... D, typename A>
std::shared_ptr (A)>>)
decorate(A&& a)
{
    return std::make_shared (A)>>>
        (decorate_impl()(std::forward(a)));
}


which can be used like

std::shared_ptr ptr =
    decorate(scorer);


This is very close to the desired syntax (maybe better), generic with respect to number and type of arguments, and inlinable.

Here is a live example, where I've thrown some arbitrary code to fill in the missing pieces of your code so that the whole thing compiles.

Attempt 2

I have now realized that the entire approach is wrong. We have a number of class templates and we want to apply them to a given object sequentially, constructing a nested object. To do this, we have a list of the class templates and we ask a single function decorate() to do the entire job for us, using recursion.

This is not right. The class templates themselves should not be used directly. We should have a helper function to wrap a given object into each class individually, exactly what std::make_shared() does for std::shared_ptr (similarly make_tuple, make_pair etc.). So here it is:

template 
using decay = typename std::decay::type;

template 
FixZToFirstInteractionScorer> fixZToFirstInteraction(S&& scorer)
{
    return FixZToFirstInteractionScorer>(std::forward(scorer));
}

template 
ApplyEnergyCutsScorer> applyEnergyCuts(S&& scorer)
{
    return ApplyEnergyCutsScorer>(std::forward(scorer));
}

template
std::shared_ptr> share(S&& scorer)
{
    return std::make_shared>(std::forward(scorer));
}


to be used like

std::shared_ptr ptr =
    share(fixZToFirstInteraction(applyEnergyCuts(scorer)));


See new live example, where I have both approaches and a validation that they produce the same result. In fact, I've discovered a mistake in my first attempt, which I have now corrected.

I think this is cleaner and probably easier to use. Its implementation is easier to understand (it's not so "magical" as the first attempt) and certainly easier for the compiler (so, faster to compile). Each object is created at the right time when its type is known, and no recursion is needed to find all unknown return types of deeply nested function calls as with decorate(). Maybe I was carried away by the "desired syntax" and didn't see this immediately.

Of course, this means we have to write one more function for each new scorer class template. But all these functions are so small and similar. If we are very lazy we could even write a macro for them.

Note that in this case we are also constructing the outermost Scorer before making the shared_ptr, which we didn't in the first attempt. The reason is that otherwise we would need two functions for each class template, one returning an object on the stack and another a shared_ptr. In case Scorer is a large object, a move constructor will ensure that we don't loose performance with the unnecessary object.

Attempt 3

Never giving up. New live example.

Without decorate(), without any helper functions (except share()), just this:

std::shared_ptr ptr3 =
    share(FixZToFirstInteractionScorer>(scorer));


It just works! Why? FixZToFirstInteractionScorer> expects an ApplyEnergyCutsScorer argument and we give it just a Scorer. But an ApplyEnergyCutsScorer is implicitly constructed from the Scorer. All this happens automatically without touching the code, and will work under arbitrary nesting.

We could even write a new decorate() around this idea, which would be purely metaprogramming now. I may do this, or not.

Code Snippets

template <typename S>
using result = typename std::result_of <S>::type;

template <template <typename> class E, template <typename> class... D>
struct decorate_impl
{
    template <typename A, typename N = result<decorate_impl<D...>(A)>>
    E<N> operator()(A&& a) const
    {
        return E<N>(decorate_impl<D...>()(std::forward<A>(a)));
    }
};

template <template <typename> class E>
struct decorate_impl <E>
{
    template <typename A, typename N = typename std::decay<A>::type>
    E<N> operator()(A&& a) const { return E<N>(std::forward<A>(a)); }
};

template <template <typename> class E, template <typename> class... D, typename A>
std::shared_ptr <E<result<decorate_impl<D...>(A)>>)
decorate(A&& a)
{
    return std::make_shared <E<result<decorate_impl<D...>(A)>>>
        (decorate_impl<D...>()(std::forward<A>(a)));
}
std::shared_ptr<XYZScorer> ptr =
    decorate<FixZToFirstInteractionScorer, ApplyEnergyCutsScorer>(scorer);
template <typename S>
using decay = typename std::decay<S>::type;

template <typename S>
FixZToFirstInteractionScorer<decay<S>> fixZToFirstInteraction(S&& scorer)
{
    return FixZToFirstInteractionScorer<decay<S>>(std::forward<S>(scorer));
}

template <typename S>
ApplyEnergyCutsScorer<decay<S>> applyEnergyCuts(S&& scorer)
{
    return ApplyEnergyCutsScorer<decay<S>>(std::forward<S>(scorer));
}

template<typename S>
std::shared_ptr<decay<S>> share(S&& scorer)
{
    return std::make_shared<decay<S>>(std::forward<S>(scorer));
}
std::shared_ptr<XYZScorer> ptr =
    share(fixZToFirstInteraction(applyEnergyCuts(scorer)));
std::shared_ptr<XYZScorer> ptr3 =
    share(FixZToFirstInteractionScorer<ApplyEnergyCutsScorer<Scorer>>(scorer));

Context

StackExchange Code Review Q#47083, answer score: 11

Revisions (0)

No revisions yet.