Реализация получения значения по индексу для пользовательского варианта класса - PullRequest
1 голос
/ 03 мая 2019

Мой вопрос может показаться очевидным и даже неправильным, поэтому я опишу свою проблему с самого начала. Как это часто бывает, я могу неправильно понять саму концепцию, поэтому пытаюсь сделать что-то, что должно быть сделано совершенно другим способом.

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: переименован вопрос, чтобы подчеркнуть, что я в принципе знаю, что такое шаблоны, просто не могу их приготовить = (

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...