Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trouble with storing a type tag when implementing an std::variant-like class

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?.

like image 997
Incomputable Avatar asked Sep 02 '16 15:09

Incomputable


People also ask

Does std :: variant allocate memory?

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.

Where can I use std variant?

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 .

How does STD variant work?

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.


1 Answers

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;

}
like image 133
Richard Hodges Avatar answered Sep 29 '22 17:09

Richard Hodges