【问题标题】:Trouble with storing a type tag when implementing an std::variant-like class实现类 std::variant 类时存储类型标记的问题
【发布时间】:2017-01-10 18:57:36
【问题描述】:

我的目标是写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&lt;&gt;() 来获得正确的类型。

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

编辑:

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

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

编辑:

我已经做了一个基线实现。它可以工作,但有很多缺失的功能。你可以找到它here。我很高兴收到评论,但请先阅读how do I write a good answer?

【问题讨论】:

  • 一个变体会跟踪其中存在的对象的类型。例如。通过index
  • @OlzhasZhumabek:“这不是我写的只是花哨的对齐方式吗?”不,因为你没有将它对齐到所有对齐的最大对齐的类型。 “问题是我不能让它成为 constexpr” 你必须以一种不需要它是 constexpr 的方式来编写它。毕竟,类型不能是一个常量表达式,因为variant的重点在于它存储的类型是在运行时确定的。
  • @NicolBolas,伙计,你说的我明白了。虽然我还需要一些碎片。当我将它们全部收集起来时,我会发布实现。
  • 不久前,我们对 C++ UG 中变体的棘手部分进行了一次闪电般的讨论。以下是幻灯片:slideshare.net/ComicSansMS/…

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


【解决方案1】:

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

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

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

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 */ }

希望对你有帮助。

【讨论】:

  • 这并没有解释如何获取 运行时整数 并确定要调用哪个构造函数。
【解决方案2】:

我可能会如何开始:

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

}

【讨论】:

  • 仅供参考:std::variant 是一个“永不为空”的变体。所以它不会默认构造为没有值。
  • @NicolBolas 好吧,据我所知,“几乎从不为空”,但我只想关注析构函数,因为这似乎是问题的症结所在。我认为构造函数和访问器从那里开始。
  • 所以你基本上列举了析构函数?我想过这个,但找不到办法。
  • @OlzhasZhumabek 如果您创建数组static const,那么我想您会发现它在编译时被枚举。我怀疑会有任何运行时开销。将编辑
  • @OlzhasZhumabek 是的,在 gcc 5.4 和 clang 3.8 中,静态 const 数组是在编译时生成的。在 gcc 6.2 中,它通过正常的静态创建检查(这必须是 gcc 常量折叠中的回归?)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-05-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多