Мой вопрос может показаться очевидным и даже неправильным, поэтому я опишу свою проблему с самого начала. Как это часто бывает, я могу неправильно понять саму концепцию, поэтому пытаюсь сделать что-то, что должно быть сделано совершенно другим способом.
Subj
Мне предложили написать простую пользовательскую версию std::variant
. Это ключевые функции (при реализации):
- Работает как союз типа-дафа
- Не выделяет дополнительную память динамически
- Правильно выровнен, в соответствии с содержащимися типами
- Существуют
get<T>
& get<index>
функции (в моем случае), которые могут получить данные variant
по типу & index (вот где начались проблемы)
Задача
Для достижения всей семантики копирования и перемещения необходимо, чтобы сохраненный тип был доступен, поэтому, если это не POD, будут вызваны его конструкторы и другие вещи. Также кажется, что get<index>()
зависит от той же способности, чтобы получить этот тип. Было бы хорошо ... но это получается только в конструкторе, что означает время выполнения.
Ниже мой небольшой набросок, он хранит значения и поддерживает get<T>
:
/* Here goes template magic, don't read much of it,
* should be working fine, just showing relevant part of it
************************************************************/
inline constexpr std::size_t variant_npos = -1;
template<typename _Tp, typename... _Types>
struct __index_of : std::integral_constant<size_t, 0> {};
template<typename _Tp, typename... _Types>
struct __index_of_v
{
static constexpr size_t value = __index_of<_Tp, _Types...>::value;
};
template<typename _Tp, typename _First, typename... _Rest>
struct __index_of<_Tp, _First, _Rest...> :
std::integral_constant<size_t, std::is_same<_Tp, _First>::value ? 0 : __index_of_v<_Tp, _Rest...>::value + 1> {};
template<typename _Tp, typename... _Types>
struct __variant_index_of // !! This can get an index of type in variant's pack. Works ok.
{
static constexpr size_t value = __index_of_v<_Tp, _Types...>::value == sizeof...(_Types) ? 0 : __index_of_v<_Tp, _Types...>::value;
};
//---------------------- Here goes the class --------------------------------
template<typename... Types>
class variant
{
const __type_index<Types...> __npos = static_cast<__type_index<Types...>>(variant_npos); // Never mind
public:
variant()
: _index(__npos) // "No type" index
{ }
variant(const variant<Types...>& other)
: _data(other._data)
{ }
variant(variant<Types...>&& other)
: _data(other._data)
{
other._index = variant_npos;
}
/* Constructors contain type check, because
* supporting implicit conversions is good, these
* checks don't influence the current question
************************************************/
template<class U, typename std::enable_if<__variant_index_of<std::decay_t<U>, Types...>::value != 0 >::type* = nullptr>
variant(U&& value)
: _index(__variant_index_of<U, Types...>::value)
{
new (getPtr()) U{std::move(value)};
}
template<class U, typename std::enable_if<__checker<0, U, Types...>::is_conv
&& __variant_index_of<std::decay_t<U>, Types...>::value == 0 >::type* = nullptr>
variant(U&& value)
: _index(__checker<0, U, Types...>::number_of_class)
{
using Datatype = typename __checker<0, U, Types...>::TargetClass;
new (getPtr()) Datatype{std::move(value)};
}
variant<Types...>& operator=(const variant<Types...>& other)
{
// TODO: should be improved, as well as move should be implemented
if (this != &other)
{
_data = other._data;
_index = other._index;
}
return *this;
}
std::size_t index() const
{ return _index; }
bool empty() const
{ return _index == __npos; }
private:
void* getPtr() const
{ return const_cast<void*>(static_cast<const void*>(std::addressof(_data))); }
void* getPtr()
{ return static_cast<void*>(std::addressof(_data)); }
private:
std::aligned_union_t<1, Types...> _data;
// Taken from GCC implementation, a sophisticated index type for alignment
// Assume it is an unsigned number
__type_index<Types...> _index;
static_assert(sizeof...(Types) > 0, "There must be at least one type alternative");
template<typename T, typename... OtherTypes>
friend T& get(variant<OtherTypes...>& v);
template<typename T, typename... OtherTypes>
friend const T& get(const variant<OtherTypes...>& v);
template<typename T, typename... OtherTypes>
friend T* get(variant<OtherTypes...>* v);
template<typename T, typename... OtherTypes>
friend const T* get(const variant<OtherTypes...>* v);
template<typename T, typename... OtherTypes>
friend T&& get(const variant<OtherTypes...>&& v);
};
//----------------- The latter 3 get functions are almost similar ------
template<typename T, typename... Types>
T& get(variant<Types...>& v)
{
return const_cast<T&>(get<T>(const_cast<const variant<Types...>&>(v)));
}
template<typename T, typename... Types>
const T& get(const variant<Types...>& v)
{
if ((v._index == variant_npos) || (v._index != __variant_index_of<T, Types...>::value))
throw bad_get("variant get error");
return *reinterpret_cast<T*>(v.getPtr());
}
Код не "минимальный", извините за это, но если показывает мои усилия. Вкратце: индекс типа хранится в конструкторах, также реализована вещь __variant_index_of
, так что индекс любого данного типа можно искать в параметрах шаблона variant
.
Тогда было неплохо реализовать что-то вроде:
template<size_t TypeIndex, typename... Types>
using variant_type_getter_t = typename variant_type_getter<TypeIndex, Types...>::type;
Может включать здесь возможную реализацию, но не решает проблему : произошло, что _index
является значением времени выполнения, так что шаблоны бесполезны, множество ‘this’ is not a constant expression
ошибок в время компиляции докажи это.
Потратив довольно много времени на чтение существующих источников (например, реализацию GCC), мы получаем , возможно, ошибочную уверенность в том, что использование классического стирания типа (pImpl -> шаблон дочернего элемента, параметризованного с сохраненным типом) не должно применяться здесь.
Вопрос
Итак, как гласит заголовок: возможно ли даже использовать какой-то трюк для реализации доступа к типу вариационного шаблона по его индексу времени выполнения?
Может быть, для этого требуется редизайн шаблона? Или, может быть, распределение данных в стеке не было продумано должным образом?
Заранее спасибо за любые яркие идеи =)
UPD: переименован вопрос, чтобы подчеркнуть, что я в принципе знаю, что такое шаблоны, просто не могу их приготовить = (