Incomputable
Incomputable

Reputation: 2208

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

Upvotes: 14

Views: 1126

Answers (2)

Richard Hodges
Richard Hodges

Reputation: 69854

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;

}

Upvotes: 10

Klemens Morgenstern
Klemens Morgenstern

Reputation: 832

First of all, you need to know which object is currently in the variant. If you want to get a type from it, that is not currently in it, you must throw an exception.

For the storage I use a union (as I do here to make it constexpr); you can't use the placement new operator as a constexpr so I think the union is the only actual way to do that (which means the only one I came up with). Mind you: you still need to explicitly call the destructor. Which yields the strange workaround I have, because a type used in a constexpr must be trivially destructible.

Now: you can implement a class similar to find_biggest_size which gives you the type from an int as a template parameter. I.e. something like that (incomplete example) :

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

And then you can implement the get function:

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

I hope that helps.

Upvotes: 0

Related Questions