首页 > 解决方案 > 为自定义变体类实现按索引获取值

问题描述

我的问题可能看起来很明显,甚至不正确,所以我将从一开始就描述我的问题。正如经常发生的那样,我可能会误解这个概念本身,因此试图做一些应该以完全不同的方式完成的事情。

主题

我一直面临着编写一个简单的自定义版本的std::variant. 它的主要功能(在实施时)是:

问题

为了实现所有的复制和移动语义,存储类型必须可用,这样如果它不是 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:重命名了要强调的问题,我基本上知道,什么是模板,只是不能煮它们=(

标签: c++variadic-templatesvariant

解决方案


推荐阅读