在实现类似 std::variant 的 class 时存储类型标签时出现问题
Trouble with storing a type tag when implementing an std::variant-like class
我的目标是写 std::variant
,可能还不够完善,但至少有完整的 constructor/destructor 对和 std::get<>()
功能。
我尝试使用字符数组预留内存。它的大小由最大的类型决定,最大的类型是使用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. I would be glad to receive a review, but please first read how do I write a good answer?。
首先,您需要知道变体中当前是哪个对象。如果你想从中获取一个当前不在其中的类型,你必须抛出异常。
对于存储,我使用联合(就像我here 那样constexpr
);您不能将 placement new 运算符用作 constexpr
所以我认为联合是唯一可行的方法(这意味着我想出的唯一方法)。请注意:您仍然需要显式调用析构函数。这产生了我的奇怪解决方法,因为 constexpr
中使用的类型必须是微不足道的可破坏的。
现在:您可以实现类似于 find_biggest_size
的 class,它将 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 */ }
希望对您有所帮助。
我可能会如何开始:
#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
,可能还不够完善,但至少有完整的 constructor/destructor 对和 std::get<>()
功能。
我尝试使用字符数组预留内存。它的大小由最大的类型决定,最大的类型是使用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. I would be glad to receive a review, but please first read how do I write a good answer?。
首先,您需要知道变体中当前是哪个对象。如果你想从中获取一个当前不在其中的类型,你必须抛出异常。
对于存储,我使用联合(就像我here 那样constexpr
);您不能将 placement new 运算符用作 constexpr
所以我认为联合是唯一可行的方法(这意味着我想出的唯一方法)。请注意:您仍然需要显式调用析构函数。这产生了我的奇怪解决方法,因为 constexpr
中使用的类型必须是微不足道的可破坏的。
现在:您可以实现类似于 find_biggest_size
的 class,它将 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 */ }
希望对您有所帮助。
我可能会如何开始:
#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;
}