My aim is to write std::variant
, may be not full blown, but at least with fully working constructor/destructor pair and std::get<>()
function.
I tried to reserve a memory using char array. The size of it is determined by the biggest type, which is found by using find_biggest_size<>()
function. The constructor uses static assert, because it performs check if the type is in the list of specified types. For now, the constructor and in place constructor works.
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?
}
};
Then I've stumbled upon a problem. I don't know what destructor to execute when the object dies. On top of that, it is impossible to access the underlying object, since I can't specialize std::get<>()
to get the right type.
My question is: how to store the type after the creation of the object? Is it the right approach? If not, what should I use?
EDIT:
I tried to apply the comments. The problem is that the index of the type that is currently alive can't be constexpr
, thus I can't extract the needed type from type list and invoke appropriate destructor.
~variant()
{
using T = typename extract<index, alternatives...>::type;
(T*)&object[0]->~T();
}
EDIT:
I've made a baseline implementation. It works, but has lots of missing features. You can find it here. I would be glad to receive a review, but please first read how do I write a good answer?.
No, very explicitly. From [variant. variant]: Any instance of variant at any given time either holds a value of one of its alternative types, or it holds no value.
union s save memory because they allow a single piece of memory to be used for different types of objects at different times. Consequently, they can be used to save memory when we have several objects that are never used at the same time. std::variant uses the memory similar to the union .
std::variant (C++17)An instance of std::variant has a value from one of its types. The value must not be a reference, C-array or void. A std::variant can have one type more than once. A default-initialized std::variant will be initialized with its first type.
How I'd probably start:
#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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With