在实现类似std :: variant的类时存储类型标记的麻烦

时间:2016-09-02 15:37:30

标签: c++ templates variant c++17

我的目标是编写std::variant,可能没有完整,但至少有完全工作的构造函数/析构函数对和std::get<>()函数。

我尝试使用char数组保留内存。它的大小由最大类型决定,可以使用find_biggest_size<>()函数找到。构造函数使用静态断言,因为它执行检查类型是否在指定类型的列表中。现在,构造函数和就地构造函数都可以工作。

template <typename ... alternatives>
class variant
{
    char object[find_biggest_size<alternatives...>::value];
public:
    template <typename T>
    variant(T&& other)
    {
        static_assert(is_present<T, alternatives...>::value, "type is not in range");
        new ((T*)&object[0]) T(std::forward<T>(other));
    }

    template <typename T, typename ... ArgTypes>
    variant(in_place_t<T>, ArgTypes&& ... args)
    {
        static_assert(is_present<T, alternatives...>::value, "type is not in range");
        new ((T*)&object[0]) T(std::forward<ArgTypes>(args)...);
    }

    ~variant()
    {
        // what to do here?
    }
};

然后我偶然发现了一个问题。我不知道当对象死亡时要执行什么析构函数。最重要的是,不可能访问底层对象,因为我无法专门化std::get<>()来获得正确的类型。

我的问题是:如何在创建对象后存储类型?这是正确的方法吗?如果没有,我应该使用什么?

修改

我试图应用评论。问题是当前存活的类型的索引不能是constexpr,因此我无法从类型列表中提取所需的类型并调用适当的析构函数。

~variant()
{
    using T = typename extract<index, alternatives...>::type;
    (T*)&object[0]->~T();
}

修改

我做了一个基线实现。它有效,但有很多缺少的功能。你可以找到它here。我很乐意收到评论,但请先阅读how do I write a good answer?

2 个答案:

答案 0 :(得分:11)

我怎么可能开始:

#include <iostream>
#include <utility>
#include <array>

template<class...Types>
struct variant
{
    variant() {}
    ~variant()
    {
        if (type_ >= 0)
        {
            invoke_destructor(type_, reinterpret_cast<char*>(std::addressof(storage_)));
        }
    }

    template<class T> static void invoke_destructor_impl(char* object)
    {
        auto pt = reinterpret_cast<T*>(object);
        pt->~T();
    }

    static void invoke_destructor(int type, char* address)
    {
        static const std::array<void (*)(char*), sizeof...(Types)> destructors
        {
            std::addressof(invoke_destructor_impl<Types>)...
        };
        destructors[type](address);
    }

    std::aligned_union_t<0, Types...> storage_;
    int type_ = -1;

};

int main()
{
    variant<int, std::string> v;

}

答案 1 :(得分:0)

首先,您需要知道变体中当前的对象。如果你想从中获取一个类型,那么它当前不在其中,你必须抛出异常。

对于存储,我使用了一个联合(就像我here一样使constexpr);你不能使用placement new运算符作为constexpr所以我认为联合是唯一的实际方法(这意味着我想出的唯一一个)。请注意:您仍然需要显式调用析构函数。这产生了我的奇怪解决方法,因为constexpr中使用的类型必须是可以轻易破坏的。

现在:您可以实现一个类似于find_biggest_size的类,它为您提供int作为模板参数的类型。即类似的东西(不完整的例子):

template<int idx, typename ...Args>
struct get_type;

template<int idx, typename First, typename ...Rest>
struct get_type<idx, First, Rest...>
{
    using type = typename get_type<idx-1, Rest>::type;    
};

template<typename First, typename ...Rest>
struct get_type<0, First, Rest...>
{
    using type = First;
};
//plus specialization without Rest

然后你可以实现get函数:

template<int i, typename ...Args>
auto get(variant<Args...> & v) -> typename get_type<i, Args...>::type
{ /* however you do that */ }

我希望有所帮助。