[英]Type Erasure for objects containing a std::tuple in c++11
可以说我有一个通用类Container,它包含任何类型的元组,并且具有函数template<typename T> T& get<T>();
返回对元组中元素的引用。 我非常简单的实现如下所示:
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);
}
};
有没有什么好的类型擦除技术可以在不更改get函数签名的情况下将Container变成常规类? 就像在不知道元组完整类型列表的情况下调用get<T>()
? 像这样:
Struct A { int x; }
Struct B { int y; }
Struct C { int z; }
int main()
{
Container container(A(), B()); //Underlying storage is a std::tuple<A, B>
A& a = container.get<A>(); //Doesn't know the tuples type list but assumes A is in there.
C& c = container.get<C>(); //C isn't in the tuples type list, crash program, which would be correct behavior.
}
boost::any
是解决这类问题的常用方法,但是并不能解决这个特殊问题,因为我必须知道要强制转换的基本元组的实际类型。 就像我在上面的示例中尝试使用它一样,我会进行boost::any_cast<std::tuple<A, B>>
来获得A或B,这对我来说没有用,因为我有意尝试隐藏元组类型列表。
编辑: TypeIndex的完整定义。
#include <type_traits>
template <typename T, typename... Ts>
struct TypeIndex;
template <typename T, typename... Ts>
struct TypeIndex<T, T, Ts...> : std::integral_constant<std::size_t, 0> {};
template <typename T, typename U, typename... Ts>
struct TypeIndex<T, U, Ts...> : std::integral_constant<std::size_t, 1 + TypeIndex<T, Ts...>::value> {};
您可以使用typeid(T)::hash_code()
而不是手写的TypeIndex<T, Ts...>::value
并将数据存储在std::unordered_map<size_t, boost::any>
。
std::tuple
不存储有关基础类型的信息。 该信息以元组的类型编码。 因此,如果您的get
方法无法知道元组的类型,那么它就无法在存储值的位置获取偏移量。 因此,您必须恢复为动态方法,并且拥有一张图是最简单的方法。
比到目前为止建议的解决方案更有效的解决方案是使用std::tuple
作为实际的基础存储,从而避免使用any
或unordered_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_;
};
现在,我们只需将其包装在类型安全的包装器中:
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
};
现场检查。
这可以通过许多方式进一步扩展,例如,您可以添加一个采用实际元组的构造函数,可以按索引访问并将值打包在std::any
等中。
如果可以使用boost::any
,则可以使用vector
或unordered_map
。 这是使用unordered_map
实现的版本:
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;
};
然后,您可以按照您的要求编写。 我将构造函数更改为接受元组,以避免使用大量的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';
}
您还可以指示您的Container
提交std::terminate()
而不是引发异常。
#include <iostream>
struct tuple_base {
virtual ~tuple_base() {}
};
template <typename T>
struct leaf : virtual tuple_base {
leaf(T const & t) : value(t) {}
virtual ~leaf() {}
T value;
};
template <typename ... T>
struct tuple : public leaf<T> ... {
template <typename ... U>
tuple(U && ... u) : leaf<T>{static_cast<U&&>(u)} ... {}
};
struct container {
tuple_base* erased_value;
template <typename T>
T & get() {
return dynamic_cast<leaf<T>*>(erased_value)->value;
}
};
int main() {
container c{new tuple<int, float, char>{1, 1.23f, 'c'}};
std::cout << c.get<float>() << std::endl;
}
关键是您必须了解有关元组类型的结构的更多信息。 仅使用单个类型包含的单个类型就不可能从类型擦除的任意元组实现中提取信息。 这更多是一种概念证明,尽管它可以解决您的要求,但使用其他方法可能会更好。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.