views:

160

answers:

2

As a personal exercise, I want to implement the visitor pattern using shared_ptr. I am familiar with Robert Martin's acyclic visitor paper but find the intrusive nature of the virtual accept() and necessary creation of an {X}Visitor class for each {X} class unpleasant. I like the boost::static_visitor class as it encapsulates all the logic locally without the necessity of {X}::accept() and {X}Visitor.

What I am looking for is a hint (as I said, I'm doing this as an exercise) of how to create the template function function rip I mention below. I think it should be of the form:

template <typename U, typename T1, typename T2, ...>
boost::variant<T1, T2, ...> rip(U& p, boost::static_visitor<T1, T2, ...> sv)
{
    if (T1 t1 = dynamic_cast<T1>(p)) return boost::variant<T1, ...>(t1);
    ... and so on, splitting static_visitor
    return 0;  // or throw an exception
}

Any hints or pointers to tutorials doing similar things would be appreciated. Thanks.

#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <memory>
#include <boost/bind.hpp>
#include <boost/variant.hpp>


struct Base {};
struct A : Base {};
struct B : Base {};
struct C : Base {};

typedef std::shared_ptr<Base> base_ptr;
typedef boost::variant<A*,B*,C*> base_variant;
struct variant_visitor : public boost::static_visitor<void> {
    void operator()(A*, base_ptr) const {std::cout << "A*\n";}
    void operator()(B*, base_ptr) const {std::cout << "B*\n";}
    void operator()(C*, base_ptr) const {std::cout << "C*\n";}
};


int main(int, char**)
{
    // This works, of course.
    base_ptr b(new A());
    base_variant v(new A());
    boost::apply_visitor(boost::bind(variant_visitor(), _1, b), v);

    // How could we use a shared_ptr with a variant?  I almost see
    // the template magic, a function to iterate over the template
    // types from the variant_visitor and return an "any<...>".
    // base_variant rip(base_ptr&, variant_visitor) {...}
    // boost::apply_visitor(boost::bind(variant_visitor(), _1, b), rip(b, variant_visitor()));

    return EXIT_SUCCESS;
}
A: 

I may be misunderstanding the question, but if you want to use the same variant_visitor for a variant containing shared pointers instead of plain pointers, perhaps this can be achieved with another visitor that obtains the pointer from the shared_ptr and passes it on to the other visitor.

#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <boost/shared_ptr.hpp>
#include <boost/bind.hpp>
#include <boost/variant.hpp>


struct Base {};
struct A : Base {};
struct B : Base {};
struct C : Base {};

typedef boost::shared_ptr<Base> base_ptr;
typedef boost::variant<boost::shared_ptr<A>,boost::shared_ptr<B>,boost::shared_ptr<C> > base_variant;

template <class Visitor>
struct visit_shared_ptr_get: public boost::static_visitor<typename Visitor::result_type>
{
    //for unary visitors
    template <class FirstArg>
    typename Visitor::result_type operator()(FirstArg& first) const
    {
        return Visitor()(first.get());
    }  

    //for binary visitors, only the first argument is "ripped"
    template <class FirstArg, class SecondArg>
    typename Visitor::result_type operator()(FirstArg& first, SecondArg& second) const
    {
        return Visitor()(first.get(), second);
    }  
};

struct variant_visitor : public boost::static_visitor<void> {
    void operator()(A*, base_ptr) const {std::cout << "A*\n";}
    void operator()(B*, base_ptr) const {std::cout << "B*\n";}
    void operator()(C*, base_ptr) const {std::cout << "C*\n";}
};


int main(int, char**)
{
    // This works, of course.
    base_ptr b(new A());
    base_variant v(boost::shared_ptr<A>(new A()));
    boost::apply_visitor(boost::bind(visit_shared_ptr_get<variant_visitor>(), _1, b), v);
    return EXIT_SUCCESS;
}

Edit: a downcaster that you seem to be envisioning.

#include <stdexcept>
#include <boost/type_traits.hpp>
#include <boost/utility/enable_if.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/variant.hpp>
#include <boost/variant/variant_fwd.hpp>
#include <boost/preprocessor/repetition.hpp>

//dynamic_cast will only compile if the target type is a pointer
template <class Derived, class Base, class Variant>
typename boost::enable_if<boost::is_pointer<Derived>, bool>::type cast_if_pointer( Base* b, Variant& variant)
{
    if (Derived p = dynamic_cast<Derived>(b)) { variant = p; return true; }
    return false;
}

//weeds out boost's unused template parameters and other non-pointers
template <class Derived, class Base, class Variant>
typename boost::disable_if<boost::is_pointer<Derived>, bool>::type cast_if_pointer( Base*, Variant& )
{
    return false;
}

template <class P, BOOST_VARIANT_ENUM_PARAMS(class T)>
void rip(boost::variant<BOOST_VARIANT_ENUM_PARAMS(T)>& variant, const boost::shared_ptr<P>& smart_ptr)
{
#define ATTEMPT_CAST(z, n, type) if (cast_if_pointer<T ## n >(smart_ptr.get(), variant)) return;
    BOOST_PP_REPEAT(BOOST_VARIANT_LIMIT_TYPES, ATTEMPT_CAST, T)
#undef ATTEMPT_CAST
    throw std::bad_cast();
}

struct Base 
{
    virtual ~Base() {}
};

struct A : Base {};
struct B : Base {};
struct C : Base {};


typedef boost::shared_ptr<Base> base_ptr;
typedef boost::variant<A*,B*,C*> base_variant;


int main(int, char**)
{
    base_ptr b(new A());
    base_variant v;
    rip(v, b);

    return EXIT_SUCCESS;
}
visitor
Thanks for the answer. See my comment to UncleBen's solution. What I'm playing with is how to convert a base_ptr to an unnamed any<T1, ...> value whose sole purpose is to act as a discriminator to the variant_visitor::operator(). The any<T1, ...> value is not used; the operator() will use the base_ptr passed it.
themis
@themis: So the thing that you are looking for is a way to **downcast** a base pointer to a suitable pointer type in a variant? I've added something with the preprocessor library, but I'd have big doubts about it. It likely has rather poor performance and can give false results, should a base type be listed before the most derived type.
visitor
A: 

You can't use the visitor for type deduction, because it has only one template argument - for the result_type. Its operator() either can or cannot be invoked on all the types any arbitrary boost::variant might be storing.

I'm only guessing the following might be what you are looking for (my first use of boost's preprocessor library):

#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <memory>
#include <boost/bind.hpp>
#include <boost/variant.hpp>

#include <boost/variant/variant_fwd.hpp>
#include <boost/preprocessor/repetition.hpp>

#define SHARED_PTR_TO_PTR(z, n, text) BOOST_PP_COMMA_IF(n) typename shared_ptr_to_ptr<text ## n>::type

template <class T>
struct shared_ptr_to_ptr { typedef T type; };

template <class T>
struct shared_ptr_to_ptr<std::shared_ptr<T> > {typedef T* type; };

template <class T>
struct unsmartify_variant;


template <BOOST_VARIANT_ENUM_PARAMS(typename T)>
struct unsmartify_variant<boost::variant<BOOST_VARIANT_ENUM_PARAMS(T) > >
{
    typedef boost::variant<BOOST_PP_REPEAT(BOOST_VARIANT_LIMIT_TYPES, SHARED_PTR_TO_PTR, T)> type;
};

template <class Variant>
struct get_visitor: boost::static_visitor<typename unsmartify_variant<Variant>::type >
{
    template <class SharedPtr>
    typename unsmartify_variant<Variant>::type operator()(SharedPtr& p) const
    {
        return p.get();
    }
};

template <class Variant>
typename unsmartify_variant<Variant>::type rip(Variant& variant)
{
    return boost::apply_visitor(get_visitor<Variant>(), variant);
}

struct Base {};
struct A : Base {};
struct B : Base {};
struct C : Base {};

typedef std::shared_ptr<Base> base_ptr;
typedef boost::variant<std::shared_ptr<A>,std::shared_ptr<B>,std::shared_ptr<C> > base_variant;

struct variant_visitor : public boost::static_visitor<void> {
    void operator()(A*, base_ptr) const {std::cout << "A*\n";}
    void operator()(B*, base_ptr) const {std::cout << "B*\n";}
    void operator()(C*, base_ptr) const {std::cout << "C*\n";}
};

int main(int, char**)
{
    // This works, of course.
    base_ptr b(new A());
    base_variant v(std::shared_ptr<A>(new A()));
    unsmartify_variant<base_variant>::type k = rip(v);
    boost::apply_visitor(boost::bind(variant_visitor(), _1, b), k);
    return EXIT_SUCCESS;
}

The greatest problem is that boost::variant takes a fixed number of arguments.

Also it appears that the variant is passed to apply_visitor by non-const reference, meaning the result of rip must be stored in a named variable.

UncleBens
Thanks for showing the techniques in unsmartify_variant. I was ignorant of those Boost features and am digging into them now. The function rip() sketched in the original code had as its sole purpose the conversion of a base_ptr into an any<A*,B*,C*> so that that any<> would be used to discriminate which variant_visitor::operator() would be invoked with the base_ptr. The any<A*,B*,C*> is an ephermal value; I want the base_ptr to be the only way to access the Base*.
themis
You're right, the definition of rip() in the original is incorrect because types cannot be inferred from the visitor type. Defining rip() to take an any<> type or calling rip<A,B,C>(base_ptr) returning any<A*,B*,C*> would be more correct.
themis