views:

93

answers:

3

Hi everybody,

I am developing the design of an application and I thought I might apply some sort of the Visitor design pattern, but it turned out that it's not exactly what I am looking for. Maybe someone can point me to the variant I require in this case?

Much of my code has a template argument "ContainerType" like

template <class ContainerType>
class MyClass
{
public:
  void doSomething(ContainerType& container) { ... }
};

There is a currently small but growing number of "Containers" that usually share many data fields.

template<class ContainedType>
struct ContainerBase 
{ 
  ContainedType data;
};

struct Container1: ContainerBase<A>, ContainerBase<B>
{};
struct Container2: ContainerBase<A>, ContainerBase<C>
{};

Container1 and Container2 are now used as template arguments for MyClass (and others), where A,B,C are some defined classes. (I have some method to do something like get<A>(container) to access the contained data. This design provides compile-time safety that MyClass may be used with all container types that contain the required types.)

Now I would like to add the feature that "if the Container contains a certain type (e.g. A), then do something, otherwise do nothing".

This can be done with something that looks like the visitor (but note that no virtual methods are used). It even allows "if the Container contains A do this, if it contains D do something else, otherwise do nothing". This could be done with

template <class ContainerType>
class MyClass
{
public:
    void doSomething(ContainerType& container) 
    { 
        container.accept(*this); 
    }

    void visit(B& b){...}
    void visit(D& d){...}

    template<typename T>
    void visit(T& t){}
};


struct Container1: ContainerBase<A>, ContainerBase<B>
{
    template<class T>
    void accept(T& t)
    {
        t.visit(ContainerBase<A>::data);
        t.visit(ContainerBase<B>::data);
    }
};

This is what I wanted, but I am looking for a better way to do it, because the implementation shown here requires implementing accept for every ContainerType. If someone derives from Container1 and ContainerBase<D> but forgets to expand the accept methods, things will become bad. Even worse, I will require a const and non-const version of accept and some containers contain >5 types, so that won't look pretty either.

All container classes are build up by inheriting from ContainerBase<T> multiple times, so I wondered if I could use this structure to implement the accept (and accept(..) const) in the ContainerBase class? I already looked at Lokis typelists but I don't know how to use them here. Do you have any idea?

Or is it possible to do this thing without the visitor-like structure?

Thanks a lot!

EDIT: I know I could go with RTTI but I'd like to avoid runtime checks and virtual methods if possible.

A: 

The variant you require in this case might well be Boost.Variant :-)

usta
It looks very similiar, but in fact it's not: boost variant is a union container (contains either one or the other), whereas my Containers contain multiple types (one AND the other). :-(
Philipp
@Philipp Hmm... Yes, now I see that. Typelists are a workable approach though, let me see what I can come up with...
usta
+1  A: 

You could use boost::mpl to define a typelist of contained types as follows:

typedef boost::mpl::vector<A, B, C> ContainedTypes;

With boost::mpl::for_each you can call a functor for each of these contained types.

e.g.

template<class U>
class Visitor
{
public:
  Visitor(MyClass<U>& item) : _item(item)
{}

template<class T>
void operator() (T)
{
  // Do what ever you want, this may be specialized as needed
}

private:
  MyClass<U>& item;
}

then call

boost::mpl::for_each<ContainedTypes> (Visitor(MyClass<ContainerType>& item))

This will invoke the operator() of Visitor for each class in ContainedTypes. The downside of this approach with specialization is that you will need to specialize operator() for combinations of T and U.

Hope this helps,

Martin

Martin
Thanks, Martin! Seems like a doable solution, but I wonder if I could avoid repeating the ContainedTypes list in both the inheritance structure (when deriving from ContainerBase<A>, ContainerBase<B>...) and the typedef "ContainedTypes" (I would place this typedef inside the Container)?
Philipp
You need at least one list of possible visited types. If you want to avoid the explicit list in "ContainedTypes", you could generate that list from the list of MyClass instantiations. But then you would have to list all those somewhere. From that list you could generate a typelist, probably a boost::mpl::set to avoid duplicates.The code for this list generation is rather tricky, so I would rather go for the list of ContainedTypes.
Martin
+1  A: 

If you can change the way container classes are defined, it looks like it could be very easy to achieve with Boost.Fusion

For Example

#include <iostream>

#include <boost/fusion/container/vector.hpp>
#include <boost/fusion/algorithm/iteration/for_each.hpp>

struct A {};
struct B {};
struct C {};

namespace fusion = boost::fusion;

struct Container1 : fusion::vector< A, B > {};

struct Container2 : fusion::vector< A, C > {};

struct doSomethingWithData {
    void operator()( B &b ) const
    {
        std::cout << "do something with B" << std::endl;
    }

    void operator()( C &c ) const
    {
        std::cout << "do something with C" << std::endl;
    }

    template < typename T >
    void operator()( T &t ) const
    {
        std::cout << "Neither B nor C" << std::endl;
    }
};

template < typename ContainerType >
void doSomething( ContainerType &container )
{
    fusion::for_each( container, doSomethingWithData() );
}

int main()
{
    Container1 c1;
    doSomething( c1 );
    std::cout << "----------------------" << std::endl;
    Container2 c2;
    doSomething( c2 );
}
usta
thanks, now that definitely works! I just changed the fusion::vector<> to fusion::set<> because I often want to access by type. Do you know if there is a performance problem with the at_key<> method? It seems to pretty complicated :-)
Philipp
@Philipp Well, if you mean runtime performance, then no. For compile time performance, it *might* be more of an issue, depending on the number of elements in a set and the number of sets used. More info [here](http://www.boost.org/doc/libs/1_44_0/libs/fusion/doc/html/fusion/sequence/concepts/associative_sequence.html) :-)
usta