2010-09-09 4 views
1

Как личное упражнение, я хочу реализовать шаблон посетителя с помощью shared_ptr. Я знаком с ациклическим гостевым документом Роберта Мартина, но обнаруживаю навязчивый характер виртуального accept() и необходимое создание класса {X} Visitor для каждого класса {X}. Мне нравится класс boost :: static_visitor, поскольку он инкапсулирует всю логику локально без необходимости {X} :: accept() и {X} Visitor.C++ шаблон метапрограммирования для создания boost :: variant из shared_ptr и boost :: static_visitor

Что я ищу - это подсказка (как я уже сказал, я делаю это как упражнение) о том, как создать функцию функции шаблона rip Я упоминаю ниже. Я думаю, что это должно быть в форме:

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 
} 

Любые подсказки или указатели на учебники делать подобные вещи будут оценены. Благодарю.

#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; 
} 

ответ

2

I может быть недоразумением вопрос, но если вы хотите использовать один и тот же variant_visitor для варианта, содержащего общие указатели вместо обычного указателя, возможно, это может быть достигнуто с другим посетителем, который получает указатель из shared_ptr и передает его другому посетителю.

#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: downcaster, что вы, кажется, представляя.

#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; 
} 
+0

Спасибо за ответ. См. Мой комментарий к решению UncleBen. То, с чем я играю, - это преобразование base_ptr в неназванное значение , единственная цель которого - действовать как дискриминатор функции variant_visitor :: operator(). Значение не используется; оператор() будет использовать base_ptr, прошедший его. – themis

+0

@themis: То, что вы ищете, - это способ ** downcast ** базовый указатель на подходящий тип указателя в варианте? Я добавил что-то с препроцессорной библиотекой, но у меня были бы большие сомнения в этом. Вероятно, он имеет довольно низкую производительность и может давать ложные результаты, если базовый тип должен быть указан до самого производного типа. – visitor

0

Вы не можете использовать посетитель для типа удержания, потому что у него есть только один аргумент шаблона - для result_type. Его operator() либо может, либо не может быть вызван во всех типах любых произвольных boost::variant.

Я только предполагаю, что следующий может быть то, что вы ищете (мой первый использование библиотеки препроцессора BOOST в):

#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; 
} 

Самая большая проблема заключается в том, что boost::variant принимает фиксированное количество аргументов.

Также представляется, что variant передается по адресу apply_visitor с помощью ссылки, отличной от константы, что означает, что результат rip должен храниться в названной переменной.

+0

Спасибо за показ методов в unsmartify_variant. Я не знал этих особенностей Boost и сейчас копаю в них. Функция rip(), набросанная в исходном коде, имела в качестве единственной цели преобразование base_ptr в любой , так что любой <> был бы использован для распознавания того, какой вариант_исвизор :: operator() будет вызываться с помощью base_ptr. Любой является эршерным значением; Я хочу, чтобы base_ptr был единственным способом доступа к базе *. – themis

+0

Вы правы, определение rip() в оригинале неверно, потому что типы не могут быть выведены из типа посетителя. Правильнее определить rip() для любого типа <> или вызова rip (base_ptr), возвращающего любые . – themis