【问题标题】:How does boost::variant allow string constants?boost::variant 如何允许字符串常量?
【发布时间】:2012-03-07 21:07:38
【问题描述】:

所以我一直在玩类型列表,他们很有趣吗?我想做的一件事是尝试实现我自己的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 工作原理的理解中唯一真正缺失的部分。想法?

【问题讨论】:

  • 顺便说一句,为什么析构函数是virtual
  • 一种习惯的力量,每个类实例的 sizeof(void*) 不必要的成本和额外的混乱,但是阅读你的代码。
  • 变体令人着迷!挖掘variant/variant.hpp,我看到模板化构造函数调用convert_construct(),其类型合法版本在内部存储地址上应用convert_copy_into访问者。
  • @Andreas Magnusson:删除了virtual,因为它真的没有任何用处。

标签: c++ boost-variant typelist


【解决方案1】:

您不想像另一个答案建议的那样做 is_convertible 。您基本上将使用 C++ 类型特征重新实现 C++ 转换机制。相反,您可以使用已有的 C++ 基础架构。

boost 的方式是让一个类具有一个函数,该函数采用变体可以接受的每种类型。我不确定 boost 是如何用 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);
};

然后您的 operator= 和其他函数调用 constructor&lt;Types...&gt;::construct(*this, value),如果有明确的转换,那么 C++ 会为您找到它。

我写了一篇相当详细的博文来剖析这一切是如何运作的:http://thenewcpp.wordpress.com/2012/02/15/variadic-templates-part-3-or-how-i-wrote-a-variant-class/

【讨论】:

    【解决方案2】:

    您可以使用类型特征,例如 is_convertible(或 C++11 标准库版本)。

    正如@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;
    };
    

    代码未经测试,但应该可以工作(减去拼写错误)。

    【讨论】:

    • 对如何实际应用它有什么想法吗?虽然该类型确实需要是可转换的,但我不确定它是否适合代码。
    • 我可能会选择一个IndexOfBestMatch 元函数,当IndexOf 在你的ctor/operator= 中失败时会调用它。不确定boost::variant 是如何做到的,但这意味着对您的代码的改动最少。
    • @AndreasMagnusson:很有趣,我得研究一下。我很想看到一个我可以接受的更详细的答案;)。
    • @Xeo:我仍然会先寻找完全匹配,只有在没有找到时才寻找可转换匹配。考虑variant&lt;TYPELIST_2(double, int)&gt; v = 1;的情况
    • @EvanTeran: 提示:),但是 Xeo 为此努力工作,而且我太累了,无法编写元代码......
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-19
    相关资源
    最近更新 更多