2010-09-09 27 views
1

Comme exercice personnel, je veux implémenter le modèle de visiteur en utilisant shared_ptr. Je suis familier avec le journal acyclique de Robert Martin, mais je trouve la nature intrusive de la virtual accept() et la création nécessaire d'une classe {X} Visitor pour chaque classe {X} désagréable. J'aime la classe boost :: static_visitor car elle encapsule toute la logique localement sans la nécessité de {X} :: accept() et {X} Visitor. Ce que je cherche est un indice (comme je l'ai dit, je fais cela comme un exercice) de la façon de créer la fonction de fonction de modèle rip Je mentionne ci-dessous. Je pense qu'il devrait être de la forme:Métaprogrammation de template C++ pour créer un boost :: variant d'un shared_ptr et d'un boost :: static_visitor

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 
} 

Tout conseils ou des pointeurs vers des tutoriels qui font des choses semblables seraient appréciés. Merci.

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

Répondre

2

je être mal compris la question, mais si vous voulez utiliser la même variant_visitor pour une variante contenant des pointeurs partagés au lieu de pointeurs simples, peut-être cela peut être réalisé avec un autre visiteur qui obtient le pointeur de la shared_ptr et le transmet à l'autre visiteur.

#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: un downcaster que vous semblez envisager.

#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

Merci pour la réponse. Voir mon commentaire à la solution d'UncleBen. Ce que je suis en train de jouer est comment convertir une base_ptr en une valeur sans nom, dont le seul but est d'agir en tant que discriminateur pour le variant_visitor :: operator(). La valeur n'est pas utilisée; l'opérateur() utilisera la base_ptr passée. – themis

+0

@themis: Donc la chose que vous cherchez est un moyen de ** downcast ** un pointeur de base vers un type de pointeur approprié dans une variante? J'ai ajouté quelque chose avec la bibliothèque du préprocesseur, mais j'aurais de gros doutes à ce sujet. Il a probablement des performances plutôt médiocres et peut donner de faux résultats si un type de base est listé avant le type le plus dérivé. – visitor

0

Vous ne pouvez pas utiliser le visiteur pour la déduction de type, car il n'a qu'un seul argument modèle - pour la result_type. Son operator() peut ou ne peut pas être invoqué sur tous les types arbitraires boost::variant peuvent être stockés.

Je ne devine que ce qui suit est peut-être ce que vous cherchez (ma première utilisation de la bibliothèque de préprocesseur de 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; 
} 

Le plus grand problème est que boost::variant prend un nombre fixe d'arguments.

Il semble également que le variant soit passé à apply_visitor par référence non-const, ce qui signifie que le résultat de rip doit être stocké dans une variable nommée.

+0

Merci de montrer les techniques dans unsmartify_variant. J'étais ignorant de ces fonctionnalités Boost et je les creuse maintenant. La fonction rip() esquissée dans le code original avait pour seul but la conversion d'un base_ptr en un afin que tout <> soit utilisé pour discriminer quel variant_visitor :: operator() serait invoqué avec le base_ptr. L' est une valeur éphermique; Je veux que le base_ptr soit le seul moyen d'accéder à la Base *. – themis

+0

Vous avez raison, la définition de rip() dans l'original est incorrecte car les types ne peuvent pas être déduits du type de visiteur. Définir rip() pour prendre un any <> type ou appeler rip (base_ptr) renvoyant serait plus correct. – themis