Frage

Also habe ich mit Typlisten herumgespielt und meine Güte, sind sie interessant?Eines der Dinge, die ich tun wollte, war der Versuch, mein eigenes umzusetzen variant Der Unterricht dient lediglich als pädagogisches Experiment darüber, wie Typlisten funktionieren und welchen Nutzen sie haben können.So sieht mein Code derzeit aus:

#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

Und das funktioniert sehr gut!Ich kann Dinge wie die folgenden schreiben und es funktioniert großartig:

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;

Und alles funktioniert wie erwartet :-).Hier ist meine Frage.Mit boost::variant Ich kann Folgendes ohne Probleme schreiben:

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

Wenn ich mit meiner Version ähnlich schreibe:

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

Ich erhalte eine Fehlermeldung wie diese:

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

Im Wesentlichen kann es nicht gefunden werden char[12] in der Typenliste in Variante.Was seitdem Sinn macht char[12] ist tatsächlich nicht explizit als einer der Typen aufgeführt ...

Wie funktioniert boost::variant Damit das so reibungslos funktioniert? Ich habe das Gefühl, dass es das einzig wirklich fehlende Teil meines Verständnisses davon ist boost::variant funktioniert.Gedanken?

War es hilfreich?

Lösung

Sie möchten is_convertible nicht ausführen, wie in einer anderen Antwort vorgeschlagen.Im Grunde würden Sie den C++-Konvertierungsmechanismus mithilfe von C++-Typmerkmalen neu implementieren.Stattdessen können Sie die bereits vorhandene C++-Infrastruktur verwenden.

Die Art und Weise, wie Boost dies tut, besteht darin, eine Klasse mit einer Funktion zu haben, die jeden Typ annimmt, den die Variante akzeptieren kann.Ich bin mir nicht sicher, wie Boost das genau mit C++03 macht, aber in der C++11-Syntax:

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

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

Dann rufen Sie Ihren Operator = und andere Funktionen auf constructor<Types...>::construct(*this, value) und wenn es eine eindeutige Konvertierung gibt, dann findet C++ sie für Sie.

Ich habe einen ziemlich detaillierten Blog-Beitrag geschrieben, in dem ich detailliert darlege, wie das alles funktioniert: http://thenewcpp.wordpress.com/2012/02/15/variadic-templates-part-3-or-how-i-wrote-a-variant-class/

Andere Tipps

Sie können Typmerkmale nutzen wie is_convertible (oder die C++11 stdlib-Version).

Wie der Kommentar von @Andreas besagt, müssen Sie Ihren Vorlagenkonstruktor/Zuweisungsoperator ein wenig ändern, z. B. nicht nach einem bestimmten Typ suchen, sondern nach der ersten Übereinstimmung.

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

Der Code ist ungetestet, sollte aber funktionieren (abzüglich Tippfehler).

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top