C++ c+;中包含std::tuple的对象的类型擦除+;11

C++ c+;中包含std::tuple的对象的类型擦除+;11,c++,c++11,C++,C++11,假设我有一个泛型类容器,它包含任何类型的元组,并且有一个函数template T&get()。我的非常简单的实现如下所示: template<typename... Ts> class Container { std::tuple<Ts...> contents; public: Container(const Ts&... ts) : contents(ts...) {} template <typename T>

假设我有一个泛型类容器,它包含任何类型的元组,并且有一个函数
template T&get()。我的非常简单的实现如下所示:

template<typename... Ts>
class Container
{
    std::tuple<Ts...> contents;

    public:
    Container(const Ts&... ts) : contents(ts...) {}

    template <typename T>
    T& get()
    {
        //TypeIndex is some meta-programming struct to find index of T in Ts
        return std::get<TypeIndex<T, Ts...>::value>(contents);
    }
};

您可以使用
typeid(T)::hash_code()
并将数据存储在
std::unordered_映射中,而不是手写
TypeIndex::value


std::tuple
不存储有关底层类型的信息。该信息以元组类型编码。因此,如果您的
get
方法无法知道元组的类型,那么它就无法在存储值的位置获取偏移量。因此,您必须恢复到动态方法,拥有一个映射是最简单的方法

如果你可以使用
boost::any
,你可以使用
向量
无序映射
。下面是一个使用无序映射实现的版本:

class Container
{
public:
    template<typename... Ts>
    Container(std::tuple<Ts...>&& t)
    {
        tuple_assign(std::move(t), data, std::index_sequence_for<Ts...>{});
    }

    template<typename T>
    T get()
    {
        auto it = data.find(typeid(T));

        if(it == data.cend()) {
            throw boost::bad_any_cast{};
        } else {
            return boost::any_cast<T>(it->second);
        }
    }

private:
    std::unordered_map<std::type_index, boost::any> data;
};
类容器
{
公众:
模板
容器(std::tuple&&t)
{
元组赋值(std::move(t),数据,{}的std::index\u序列);
}
模板
不明白
{
autoit=data.find(typeid(T));
if(it==data.cend()){
抛出推进::bad_any_cast{};
}否则{
返回升压::任意施法(它->秒);
}
}
私人:
std::无序的地图数据;
};
然后你就可以写你的请求了。我将构造函数更改为接受元组,以避免使用大量sfinae代码来防止重写复制/移动构造函数,但如果您愿意,可以这样做

Container c(std::make_tuple(1, 1.5, A{42}));

try {
    std::cout << "int: " << c.get<int>() << '\n';
    std::cout << "double: " << c.get<double>() << '\n';
    std::cout << "A: " << c.get<A>().val << '\n';
    c.get<A&>().val = 0;
    std::cout << "A: " << c.get<A>().val << '\n';
    std::cout << "B: " << c.get<B>().val << '\n'; // error
} catch (boost::bad_any_cast const& ex) {
    std::cout << "exception: " << ex.what() << '\n';
}
容器c(std::make_tuple(1,1.5,A{42})); 试一试{ std::cout
#包括
结构元组库{
虚~tuple_base(){}
};
模板
结构叶:虚拟元组库{
叶(T常数&T):值(T){}
虚拟~leaf(){}
T值;
};
模板
结构元组:公共叶{
模板
元组(U&&…U):叶{static_cast(U)}…{
};
结构容器{
元组_基*擦除的_值;
模板
T&get(){
返回动态_cast(擦除的_值)->值;
}
};
int main(){
容器c{新元组{1,1.23f,'c'};

std::cout一个比目前提出的解决方案稍微有效的解决方案是使用
std::tuple
作为实际的底层存储,从而避免使用
any
unordered\u map

如果我们使用经典类型擦除模式,我们只需要一个动态分配(加上复制实际对象所需的任何内容),或者如果实现小型缓冲区优化,则需要零

我们首先定义一个基本接口来按类型访问元素

struct base
{
    virtual ~base() {}

    virtual void * get( std::type_info const & ) = 0;
};
我们使用
void*
而不是
any
来返回对对象的引用,从而避免了复制和可能的内存分配

实际存储类派生自
base
,并根据其可以包含的参数进行模板化:

template<class ... Ts>
struct impl : base
{
    template<class ... Us>
    impl(Us && ... us) : data_(std::forward<Us>(us) ... ) 
    {
        //Maybe check for duplicated types and throw.
    }

    virtual void * get( std::type_info const & ti ) 
    {
        return get_helper( ti, std::index_sequence_for<Ts...>() );
    }

    template<std::size_t ... Indices>
    void* get_helper( std::type_info const & ti, std::index_sequence<Indices...> )
    {
        //If you know that only one element of a certain type is available, you can refactor this to avoid comparing all the type_infos
        const bool valid[] = { (ti == typeid(Ts)) ... };

        const std::size_t c = std::count( std::begin(valid), std::end(valid), true );
        if ( c != 1 )
        {
            throw std::runtime_error(""); // something here
        }

        // Pack the addresses of all the elements in an array
        void * result[] = { static_cast<void*>(& std::get<Indices>(data_) ) ... };

        // Get the index of the element we want
        const int which = std::find( std::begin(valid), std::end(valid), true ) - std::begin(valid);

        return result[which];
    }

    std::tuple<Ts ... > data_;
};
模板
结构impl:base
{
模板
impl(美国和…美国):数据(标准::转发(美国).)
{
//可能检查重复的类型并抛出。
}
虚拟void*get(std::type_info const&ti)
{
返回get_helper(ti,std::index_sequence_for());
}
模板
void*get\u helper(std::type\u info const&ti,std::index\u序列)
{
//如果您知道某个类型只有一个元素可用,那么可以重构它以避免比较所有类型的信息
const bool valid[]={(ti==typeid(Ts))…};
const std::size\u t c=std::count(std::begin(有效)、std::end(有效)、true);
如果(c!=1)
{
抛出std::runtime\u错误(“”;//此处有内容
}
//将数组中所有元素的地址打包
void*result[]={static_cast(&std::get(data_))…};
//获取所需元素的索引
const int=std::find(std::begin(有效)、std::end(有效)、true)-std::begin(有效);
返回结果[其中];
}
std::元组数据;
};
现在,我们只需将其包装在类型安全包装中:

class any_tuple
{
public:
     any_tuple() = default; // allow empty state

     template<class ... Us>
     any_tuple(Us && ... us) :
            m_( new impl< std::remove_reference_t< std::remove_cv_t<Us> > ... >( std::forward<Us>(us) ... ) )
       {}

     template<class T>
     T& get()
     {
        if ( !m_ )
        {
            throw std::runtime_error(""); // something
        }
        return *reinterpret_cast<T*>( m_->get( typeid(T) ) );
     }

     template<class T>
     const T& get() const
     {
         return const_cast<any_tuple&>(*this).get<T>();
     }

     bool valid() const { return bool(m_); }

 private:
     std::unique_ptr< base > m_; //Possibly use small buffer optimization
 };
对任何元组进行分类
{
公众:
any_tuple()=default;//允许空状态
模板
任何元组(Us&…Us):
m(新的impl..>(std::forward(美国)…)
{}
模板
T&get()
{
如果(!m_)
{
抛出std::runtime\u错误(“”;//something
}
return*reinterpret\u cast(m\uu->get(typeid(T));
}
模板
常量T&get()常量
{
返回const_cast


这可以通过许多方式进一步扩展,例如,您可以添加一个采用实际元组的构造函数,您可以通过索引访问并将值打包到
std::any
,等等。

我假设
TypeIndex
是您定义的函数吗?它是
constepr
?是的,TypeIndex是我定义的从m
std::integral_constant
,它获取类型列表中类型的索引,它是
std::get();
的助手结构。因此,如果它是
constepr
,那么您不能尝试
std::enable_if
get()
函数上执行吗?另外,
TypeIndex
如何处理
std::tuple
?(编辑:我在前面的评论中提到了函数,我指的是struct。)对于std::tuple,它将返回列表中的第一个int,并且无法获取第二个int,这在我的情况下是可以的,因为假定tuple具有唯一的列表。那么,您不能使用
std::enable_if
来打开
模板t&get()< /代码>如果您用无效类型调用,则无法编译一个函数(参见:):您的建议将编译时构造转换成运行时构造,可能不是OP所需要的。虽然我不知道OP的意图。OP需要一个多态性容器。C++中有静态和动态多态性。
struct base
{
    virtual ~base() {}

    virtual void * get( std::type_info const & ) = 0;
};
template<class ... Ts>
struct impl : base
{
    template<class ... Us>
    impl(Us && ... us) : data_(std::forward<Us>(us) ... ) 
    {
        //Maybe check for duplicated types and throw.
    }

    virtual void * get( std::type_info const & ti ) 
    {
        return get_helper( ti, std::index_sequence_for<Ts...>() );
    }

    template<std::size_t ... Indices>
    void* get_helper( std::type_info const & ti, std::index_sequence<Indices...> )
    {
        //If you know that only one element of a certain type is available, you can refactor this to avoid comparing all the type_infos
        const bool valid[] = { (ti == typeid(Ts)) ... };

        const std::size_t c = std::count( std::begin(valid), std::end(valid), true );
        if ( c != 1 )
        {
            throw std::runtime_error(""); // something here
        }

        // Pack the addresses of all the elements in an array
        void * result[] = { static_cast<void*>(& std::get<Indices>(data_) ) ... };

        // Get the index of the element we want
        const int which = std::find( std::begin(valid), std::end(valid), true ) - std::begin(valid);

        return result[which];
    }

    std::tuple<Ts ... > data_;
};
class any_tuple
{
public:
     any_tuple() = default; // allow empty state

     template<class ... Us>
     any_tuple(Us && ... us) :
            m_( new impl< std::remove_reference_t< std::remove_cv_t<Us> > ... >( std::forward<Us>(us) ... ) )
       {}

     template<class T>
     T& get()
     {
        if ( !m_ )
        {
            throw std::runtime_error(""); // something
        }
        return *reinterpret_cast<T*>( m_->get( typeid(T) ) );
     }

     template<class T>
     const T& get() const
     {
         return const_cast<any_tuple&>(*this).get<T>();
     }

     bool valid() const { return bool(m_); }

 private:
     std::unique_ptr< base > m_; //Possibly use small buffer optimization
 };