2012-03-07 6 views
3

Donc j'ai joué avec des typelistes et des garçons sont-ils intéressants. Une des choses que je voulais faire était de tenter d'implémenter ma propre classe variant simplement comme une expérience en éducation sur le fonctionnement des typologies et comment elles peuvent être utiles. Voici à quoi ressemble mon code:Comment boost :: variant autorise les constantes de type chaîne?

#include <cstddef> 
#include <typeinfo> 

#ifndef VARIANT_H_ 
#define VARIANT_H_ 

struct NullType {}; 

template <class T, class U> 
struct TypeList { 
    typedef T Head; 
    typedef U Tail; 
}; 

#define TYPELIST_1(T1)         TypeList<T1, NullType> 
#define TYPELIST_2(T1, T2)        TypeList<T1, TYPELIST_1(T2) > 
#define TYPELIST_3(T1, T2, T3)       TypeList<T1, TYPELIST_2(T2, T3) > 
#define TYPELIST_4(T1, T2, T3, T4)      TypeList<T1, TYPELIST_3(T2, T3, T4) > 
#define TYPELIST_5(T1, T2, T3, T4, T5)     TypeList<T1, TYPELIST_4(T2, T3, T4, T5) > 
#define TYPELIST_6(T1, T2, T3, T4, T5, T6)    TypeList<T1, TYPELIST_5(T2, T3, T4, T5, T6) > 
#define TYPELIST_7(T1, T2, T3, T4, T5, T6, T7)   TypeList<T1, TYPELIST_6(T2, T3, T4, T5, T6, T7) > 
#define TYPELIST_8(T1, T2, T3, T4, T5, T6, T7, T8)  TypeList<T1, TYPELIST_7(T2, T3, T4, T5, T6, T7, T8) > 
#define TYPELIST_9(T1, T2, T3, T4, T5, T6, T7, T8, T9) TypeList<T1, TYPELIST_8(T2, T3, T4, T5, T6, T7, T8, T9) > 

namespace util { 

    namespace { 
     template <class TL>     struct MaxSize; 
     template <class TL>     struct Length; 
     template <class TL, class T>  struct IndexOf; 
     template <class TL, unsigned int i> struct TypeAt; 

     template <> 
     struct MaxSize<NullType> { 
      static const size_t value = 0; 
     }; 

     template <class Head, class Tail> 
     struct MaxSize<TypeList<Head, Tail> > { 
      static const size_t value = (sizeof(Head) > MaxSize<Tail>::value) ? sizeof(Head) : MaxSize<Tail>::value; 
     }; 

     template <> 
     struct Length<NullType> { 
      enum { value = 0 }; 
     }; 

     template <class Head, class Tail> 
     struct Length<TypeList<Head, Tail> > { 
      enum { value = 1 + Length<Tail>::value }; 
     }; 

     template <class T> 
     struct IndexOf<NullType, T> { 
      enum { value = -1 }; 
     }; 

     template <class Tail, class T> 
     struct IndexOf<TypeList<T, Tail>, T> { 
      enum { value = 0 }; 
     }; 

     template <class Head, class Tail, class T> 
     struct IndexOf<TypeList<Head, Tail>, T> { 
      enum { value = (IndexOf<Tail, T>::value == -1) ? -1 : 1 + IndexOf<Tail, T>::value }; 
     }; 

     template <class Head, class Tail> 
     struct TypeAt<TypeList<Head, Tail>, 0> { 
      typedef Head type; 
     }; 

     template <class Head, class Tail, unsigned int i> 
     struct TypeAt<TypeList<Head, Tail>, i> { 
      typedef typename TypeAt<Tail, i - 1>::type type; 
     }; 
    } 

    template <class TL> 
    class variant; 

    template<class U, class TL> 
    U *get(variant<TL> *v); 

    template<class U, class TL> 
    const U *get(const variant<TL> *v); 

    template<class U, class TL> 
    U &get(variant<TL> &v); 

    template<class U, class TL> 
    const U &get(const variant<TL> &v); 

    // this stuff is a visitation pattern used to make sure 
    // that contained objects get properly destroyed 
    namespace { 
     template <class TL> 
     struct apply_visitor; 

     struct destroy_visitor { 
      template <class T> 
      void operator()(T *p) { 
       p->~T(); 
      } 
     }; 

     template <class H, class T> 
     struct visitor_impl { 
      template <class U, class Pred> 
      static void visit(U *p, Pred pred) { 
       if(H *x = get<H>(p)) { 
        pred(x); 
       } else { 
        apply_visitor<T>::visit(p, pred); 
       } 
      } 
     }; 

     template <class H> 
     struct visitor_impl<H, NullType> { 
      template <class U, class Pred> 
      static void visit(U *p, Pred pred) { 
       if(H *x = get<H>(p)) { 
        pred(x); 
       } else { 
        throw std::bad_cast(); 
       } 
      } 
     }; 

     template <class TL> 
     struct apply_visitor { 
      typedef typename TL::Head H; 
      typedef typename TL::Tail T; 

      template <class U, class Pred> 
      static void visit(U *p, Pred pred) { 
       visitor_impl<H, T>::visit(p, pred); 
      } 
     }; 
    } 

    template <class TL> 
    class variant { 
     template<class U, class X> friend U *get(variant<X> *v); 
     template<class U, class X> friend const U *get(const variant<X> *v); 
     template<class U, class X> friend U &get(variant<X> &v); 
     template<class U, class X> friend const U &get(const variant<X> &v); 

    public :    
     variant() : type_index_(0){ 
      new (&storage_) typename TypeAt<TL, 0>::type(); 
     } 

     ~variant() { 
      apply_visitor<TL>::visit(this, destroy_visitor()); 
     } 

     template <class T> 
     variant(const T &x) : type_index_(IndexOf<TL, T>::value) { 
      typedef typename TypeAt<TL, IndexOf<TL, T>::value>::type value_type; 
      new (&storage_) value_type(x); 
     } 

     template <class T> 
     variant(T &x) : type_index_(IndexOf<TL, T>::value) { 
      typedef typename TypeAt<TL, IndexOf<TL, T>::value>::type value_type; 
      new (&storage_) value_type(x); 
     } 

     template <class T> 
     variant &operator=(const T &rhs) { 
      variant(rhs).swap(*this); 
      return *this; 
     } 

     variant &operator=(const variant &rhs) { 
      variant(rhs).swap(*this); 
      return *this; 
     } 

    public: 
     void swap(variant &other) { 
      using std::swap; 
      swap(storage_, other.storage_); 
      swap(type_index_, other.type_index_); 
     } 

    private: 
     template <class T> 
     const T &get_ref() const { 
      typedef typename TypeAt<TL, IndexOf<TL, T>::value>::type value_type; 

      if(IndexOf<TL, T>::value != type_index_) { 
       throw std::bad_cast(); 
      } 

      return *reinterpret_cast<const value_type *>(&storage_); 
     } 

     template <class T> 
     T &get_ref() { 
      typedef typename TypeAt<TL, IndexOf<TL, T>::value>::type value_type; 

      if(IndexOf<TL, T>::value != type_index_) { 
       throw std::bad_cast(); 
      } 

      return *reinterpret_cast<value_type *>(&storage_); 
     } 

     template <class T> 
     const T *get_ptr() const { 
      typedef typename TypeAt<TL, IndexOf<TL, T>::value>::type value_type; 

      if(IndexOf<TL, T>::value != type_index_) { 
       return 0; 
      } 

      return reinterpret_cast<const value_type *>(&storage_); 
     } 

     template <class T> 
     T *get_ptr() { 
      typedef typename TypeAt<TL, IndexOf<TL, T>::value>::type value_type; 

      if(IndexOf<TL, T>::value != type_index_) { 
       return 0; 
      } 

      return reinterpret_cast<value_type *>(&storage_); 
     } 

    public: 
     int which() const { 
      return type_index_; 
     } 

     bool empty() const { 
      return false; 
     } 

     const std::type_info &type() const; 

    private: 
     struct { unsigned char buffer_[MaxSize<TL>::value]; } storage_; 
     int             type_index_; 
    }; 

    // accessors 
    template<class U, class TL> 
    U *get(variant<TL> *v) { 
     return v->template get_ptr<U>(); 
    } 

    template<class U, class TL> 
    const U *get(const variant<TL> *v) { 
     return v->template get_ptr<U>(); 
    } 

    template<class U, class TL> 
    U &get(variant<TL> &v) { 
     return v.template get_ref<U>(); 
    } 

    template<class U, class TL> 
    const U &get(const variant<TL> &v) { 
     return v.template get_ref<U>(); 
    } 
} 

#endif 

Et cela fonctionne très bien! Je peux écrire des choses comme les suivantes et cela fonctionne très bien:

typedef util::variant<TYPELIST_3(std::string, int, double)> variant; 
variant x = std::string("hello world"); 
variant y = 10; 
variant z = 123.45; 

std::cout << util::get<std::string>(x) << std::endl; 
std::cout << util::get<int>(y) << std::endl; 
std::cout << util::get<double>(z) << std::endl; 

Et tout fonctionne comme prévu :-). Voici ma question. Avec boost::variant Je peux écrire ce qui suit sans problème:

boost::variant<int, std::string> v = "hello world"; 

Avec ma version, si j'écris similaire:

util::variant<TYPELIST_2(int, std::string)> v = "hello world"; 

Je reçois une erreur comme ceci:

variant.hpp: In instantiation of 'util::<unnamed>::TypeAt<TypeList<std::basic_string<char>, NullType>, 4294967294u>': 
variant.hpp:76:47: instantiated from 'util::<unnamed>::TypeAt<TypeList<int, TypeList<std::basic_string<char>, NullType> >, 4294967295u>' 
variant.hpp:161:61: instantiated from 'util::variant<TL>::variant(const T&) [with T = char [12], TL = TypeList<int, TypeList<std::basic_string<char>, NullType> >]' 
test.cc:27:50: instantiated from here 
variant.hpp:76:47: error: invalid use of incomplete type 'struct util::<unnamed>::TypeAt<NullType, 4294967293u>' 
variant.hpp:32:46: error: declaration of 'struct util::<unnamed>::TypeAt<NullType, 4294967293u>' 

Essentiellement, il ne peut pas trouver char[12] dans la typelist en variante. Ce qui est logique puisque char[12] n'est en fait pas explicitement répertorié comme l'un des types ...

Comment fonctionne boost::variant de façon si transparente? Je me sens comme si c'est la seule vraie pièce manquante dans ma compréhension de la façon dont boost::variant fonctionne. Pensées?

+1

BTW, aucune raison pour laquelle la destructor est 'virtual'? – Xeo

+0

Aucune raison, force de l'habitude. –

+1

Une force d'habitude au coût inutile de 'sizeof (void *)' par instance de votre classe et une confusion supplémentaire pour cependant lire votre code. –

Répondre

3

Vous ne voulez pas faire is_convertible comme autre réponse suggérée. Vous réimplémenteriez fondamentalement le mécanisme de conversion C++ en utilisant des traits de type C++. Au lieu de cela, vous pouvez utiliser l'infrastructure C++ que vous avez déjà.

Le moyen de booster est d'avoir une classe avec une fonction qui prend tous les types que la variante peut accepter. Je ne sais pas comment stimuler le fait exactement avec C++ 03, mais dans 11 syntaxe C de:

template <typename First, typename... Rest> 
class constructor : public constructor<Rest...> 
{ 
    using constructor<Rest...>::construct; 

    static void 
    construct(variant& v, First&& value); 
}; 

Ensuite, votre opérateur = et d'autres fonctions et appelle constructor<Types...>::construct(*this, value) s'il y a une conversion non ambiguë alors C++ finds Pour toi.

j'ai écrit un billet de blog assez détaillé disséquer comment tout cela fonctionne: http://thenewcpp.wordpress.com/2012/02/15/variadic-templates-part-3-or-how-i-wrote-a-variant-class/

+0

Définitivement une meilleure idée, +1. – Xeo

2

Vous pouvez utiliser des caractères de type is_convertible (ou la version stdlib C++ 11). Comme le dit le commentaire de @Andreas, vous devez modifier un peu le constructeur/l'opérateur d'assignation de modèle, comme dans, ne recherchez pas un type spécifique, mais pour la première correspondance.

#include <boost/mpl/if.hpp> 
#include <boost/type_traits/is_convertible.hpp> 

template<class T, class TList> 
struct FirstMatch; 

template<class T, class Head, class Tail> 
struct FirstMatch<T, TypeList<Head, Tail>>{ 
    static bool const is_conv = boost::is_convertible<T, Head>::value; 
    typedef typename boost::mpl::if_c<is_conv, Head, 
     typename FirstMatch<T, Tail>::type>::type type; 
}; 

template<class T> 
struct FirstMatch<T, NullType>{ 
    typedef struct ERROR_no_convertible_type_found type; 
}; 

template<class T, class TList> 
struct FirstOrExactMatch{ 
    static int const idx = IndexOf<TList, T>::value; 
    typedef typename boost::mpl::if_c<idx != -1, 
     TypeAt<TList, idx>, 
     FirstMatch<T, TList> 
     >::type::type type; 
}; 

Le code n'a pas été testé, mais devrait fonctionner (moins les fautes de frappe).

+0

Avez-vous des idées sur comment l'appliquer? Bien que le type doive certainement être convertible, je ne suis pas sûr de savoir où cela correspondrait au code. –

+1

Je choisirais probablement une méta-fonction 'IndexOfBestMatch' qui serait appelée quand' IndexOf' échouerait dans votre ctor/'operator ='. Je ne sais pas comment 'boost :: variant' le fait mais cela signifierait le moindre changement pour votre code tel qu'il est aujourd'hui. –

+0

@AndreasMagnusson: intéressant, je vais devoir me pencher là-dessus. J'aimerais voir une réponse qui va dans un peu plus de détails que je peux accepter;). –

Questions connexes