2012-03-07 3 views
3

Итак, я играю со списками и мальчиком, они интересны. Одна из вещей, которую я хотел сделать, это попытаться реализовать свой собственный класс variant просто как эксперимент в обучении о том, как работают справочники и как они могут быть полезны. Вот как выглядит мой код:Как boost :: variant допускает строковые константы?

#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 

И это работает очень хорошо! Я могу написать такие вещи, как следующее, и отлично работает:

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; 

И все работает как ожидалось :-). Вот мой вопрос. С boost::variant я могу написать следующее без проблем:

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

С моей версии, если я пишу так же:

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

Я получаю сообщение об ошибке, как это:

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>' 

По существу, он не может найти char[12] в списке вариантов в варианте. Что имеет смысл, так как char[12] фактически не указано ни в одном из типов ...

Как boost::variant сделать эту работу настолько плавно? Я чувствую, что это единственный реальный недостающий кусок в моем понимании того, как работает boost::variant. Мысли?

+1

BTV, никаких причин, почему деструктор 'virtual'? – Xeo

+0

Нет причины, сила привычки. –

+1

Сила привычки при ненужной стоимости 'sizeof (void *)' на экземпляр вашего класса и дополнительная путаница для того, чтобы читать ваш код. –

ответ

3

Вы не хотите делать is_convertible, как предложил другой ответ. В основном вы должны переопределить механизм преобразования C++ с использованием свойств типа C++. Вместо этого вы можете использовать уже имеющуюся инфраструктуру C++.

Способ повышения - это наличие класса с функцией, которая принимает каждый тип, который может принять этот вариант. Я не уверен, как подталкивание делает это именно с C++ 03, но в C++ 11 синтаксиса:

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

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

Тогда ваш оператор = и другие функции вызывают constructor<Types...>::construct(*this, value) и если существует однозначное преобразование, то C++ находит это для вас.

Я написал довольно подробный блог рассекает, как все это работает: http://thenewcpp.wordpress.com/2012/02/15/variadic-templates-part-3-or-how-i-wrote-a-variant-class/

+0

Определенно лучшая идея, +1. – Xeo

2

Вы можете использовать черты типа, такие как is_convertible (или версия C++ 11 stdlib).

Как говорится в комментарии @Andreas, вам нужно немного изменить свой шаблонный оператор конструктора/присваивания, как и в, не искать определенный тип, а для первого совпадения.

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

Код не проверен, но должен работать (минус опечатки).

+0

Любые мысли о том, как на самом деле применить это? В то время как тип, безусловно, должен быть конвертируемым, я не уверен, где это будет вписываться в код. –

+1

Я бы, вероятно, пошел на мета-функцию IndexOfBestMatch, которая вызывается, когда 'IndexOf' терпит неудачу в вашем ctor /' operator = '. Не уверен, как это делает 'boost :: variant', но это будет означать наименьшее изменение в вашем коде, как сегодня. –

+0

@AndreasMagnusson: интересно, мне нужно будет изучить это. Я бы хотел увидеть ответ, который будет более подробным, чем я могу принять;). –

Смежные вопросы