Coupling Static Visitor with a Static Polymorphism Hierarchy

1.3k views Asked by At

The purpose of my program is to create a list of data which i can visit with a set of static visitors while using static polymorphism in my class hierarchy.

I have created a hierarchy of classes utilizing static polymorphism through CRTP:

class VirtualBaseData {
public:    
    //someVirtualFunction
}

template<typename Derived>
class BaseData<Derived> {
public:
    template<typename Visitor>
    void accept(Visitor &v){
         static_cast<Derived*>(this)->accept(v);
    }
}

class DerivedBaseData1: BaseData<DerivedBaseData> {
public:
    template<typename Visitor>
    void accept(Visitor &v){
         //Specific implementation
    }    
}
class DerivedBaseData2: BaseData<DerivedBaseData> {
public:
    template<typename Visitor>
    void accept(Visitor &v){
         //Specific implementation
    }    
}

I want to store the DerivedBaseData in a contain for later being iterated through and visited.

int main(){
    std::vector<VirtualBaseData*> dataSet;
    dataSet.push_back(new DerivedBaseData1);
    dataSet.push_back(new DerivedBaseData2);
    for(auto it = fifth.begin(); it != fifth.end(); ++it){
        it->accept(); //Error: VirtualBaseData does not have a member function accept
    }
}

I am looking for at a way to couple my static visitors with my static polymorphism hierarchy. I am in need of a VirtualBaseData class in my static polymorphism which is not a template class in order to use the classes in containers. However, since i can not have the VirtualBaseData class be a template class, i am unable to create the appropriate static_cast to a derived class as done in the CRTPattern.

My question is: does anyone have a good strategy which would preserves my static polymorphism setup as well as a static visitor pattern?

For reference: I have implemented my static visitors as described on page 21-23 in http://hillside.net/plop/2006/Papers/Library/portableProgrammingPL.pdf

3

There are 3 answers

1
goldstar On

error is correct, because you creating vector of VirtualBaseData pointers. Class VirtualBaseData doesn't contain accept() function.

yout answer is: Can a C++ class member function template be virtual?

also read about polymorphism: http://www.cplusplus.com/doc/tutorial/polymorphism/

4
paweldac On

As you said there is no simple solution, but you might want to use runtime polymorphism instead of static. In order to archive runtime polymorphism you need to perform type erasure on base class and dispatch to proper non-temaplte functions. This can be archived for example with boost::any from boost library. Constrain for boost::any is that all objects stored as boost::any must be copyable. If boost::any is not suitable for you read more about type erasuring.

class VirtualBaseData {
public:    
    template <typename T>
    void accept(T& visitor) {
        acceptImpl(boost::any(visitor));
    }
protected:
    virtual void acceptImpl(boost::any const & value ) = 0;
}
0
pzelasko On

If you don't know how many/what types your objects will be in compile time, then it's a use-case for dynamic polymorphism (at least, I don't know how to do it using only static polymorphism).

However... if you know at compile time exact number and types of your objects, now we're talking! Here's a minimal compiling example (code on ideone):

#include <iostream>
#include <tuple>
#include <type_traits>
using namespace std;


template<typename Derived>
class BaseData {
public:
    template<typename Visitor>
    void accept(Visitor &v){
         static_cast<Derived*>(this)->accept(v);
    }
};

class DerivedBaseData1: BaseData<DerivedBaseData1> {
public:
    template<typename Visitor>
    void accept(Visitor &v){
        std::cout << "DerivedBaseData1: accepting visitor " << v << std::endl;
    }    
};
class DerivedBaseData2: BaseData<DerivedBaseData2> {
public:
    template<typename Visitor>
    void accept(Visitor &v){
        std::cout << "DerivedBaseData2: accepting visitor " << v << std::endl;
    }    
};

namespace impl {

    template <size_t N> 
    struct num2type {};

    template <size_t Idx, typename T, typename Visitor>
    void accept_impl(Visitor &v, T &&collection, num2type<Idx>) {
        // run accept on current object
        auto &object = std::get<Idx>(collection);
        object.accept(v);
        // move iteration forward
        accept_impl(v, std::forward<T>(collection), num2type<Idx - 1>{});
    }

    template <typename T, typename Visitor>
    void accept_impl(Visitor &v, T &&collection, num2type<0>) {
        // run accept on current object
        auto &object = std::get<0>(collection);
        object.accept(v);
    }
}

template<typename ...Ts, typename Visitor>
void accept(Visitor &v, std::tuple<Ts...> &&collection) {
    using T = decltype(collection);
    impl::accept_impl(v, std::forward<T>(collection), impl::num2type<std::tuple_size<std::decay_t<T>>::value - 1>{});
}


int main() {
    using visitor_type = int;
    visitor_type visitor = 42;

    DerivedBaseData1 a1, a3;
    DerivedBaseData2 a2;
    accept(visitor, std::tie(a1, a2, a3));

    return 0;
}

Using static polymorphism you can iterate a static collection (here, a std::tuple) and call desired method with desired arguments on each of them.