Проблема с сохранением тега типа при реализации класса, подобного std::variable

Моя цель - написать std::variant, может быть не в полном объеме, но по крайней мере с полностью работающей парой конструктор / деструктор и std::get<>() функция.

Я попытался зарезервировать память, используя массив символов. Размер определяется самым большим типом, который определяется с помощью find_biggest_size<>() функция. Конструктор использует static assert, потому что он выполняет проверку, находится ли тип в списке указанных типов. На данный момент конструктор и на месте конструктор работает.

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();
}

РЕДАКТИРОВАТЬ:

Я сделал базовую реализацию. Это работает, но имеет много недостающих функций. Вы можете найти это здесь. Я был бы рад получить отзыв, но, пожалуйста, сначала прочитайте, как мне написать хороший ответ?,

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;

}

Прежде всего, вам нужно знать, какой объект в данный момент находится в варианте. Если вы хотите получить из него тип, которого в данный момент нет, вы должны выбросить исключение.

Для хранения я использую объединение (как я делаю здесь, чтобы сделать это constexpr); Вы не можете использовать новый оператор размещения в качестве constexpr так что я думаю, что объединение - единственный реальный способ сделать это (что означает единственный, который я придумал). Имейте в виду: вам все еще нужно явно вызвать деструктор. Который дает странный обходной путь, который у меня есть, потому что тип, используемый в constexpr должен быть тривиально разрушаем.

Теперь: вы можете реализовать класс, похожий на find_biggest_size который дает вам тип из int в качестве параметра шаблона. Т.е. что-то подобное (неполный пример):

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

Надеюсь, это поможет.

Другие вопросы по тегам