Проблема с сохранением тега типа при реализации класса, подобного 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 */ }
Надеюсь, это поможет.