[英]Why no “variant” any in boost or Standard?
One advantage of any
over variant
is, that one does not need to specify all types, that it may contain. 的一个优点
any
超过variant
是,一个并不需要指定所有类型,它可能含有。 I've noticed, that as the number of types a variant
may contain grows, people tend to switch to any
at some point, because they simply don't keep track of all the types anymore. 我注意到,随着
variant
可能包含的类型数量的增加,人们倾向于在某个时候切换到any
,因为他们根本不再跟踪所有类型。 I think a hybrid between any
and variant
is possible. 我认为
any
与variant
之间的混合都是可能的。 One could store the " placeholder
" (via placement new
) of any
in aligned_storage
, with the size calculated in a constexpr
function or template metafunction, from a sample of the largest types, that may end up being stored. 一个可以存储“
placeholder
”(通过放置new
)的any
在aligned_storage
,与在所计算的大小constexpr
功能或模板元函数,从最大的类型的一个样本,其可以最终被存储。 The user, on the other hand, would not need to specify all the types, that an any
might contain. 另一方面,用户不需要指定
any
可能包含的所有类型。 The any
could also throw at any time, if the user would try to store something larger than the aligned_storage
in there. 如果用户尝试在其中存储大于
aligned_storage
,则any
可能抛出。
Does such a " variant_any
" class exist? 是否存在这样的“
variant_any
”类? Is there some inherent problem with the idea? 这个想法有一些内在的问题吗?
Here is a basic some
. 这是基本的
some
。
The T
copy/assign/move/etc can be implemented in terms of emplace
. T
复制/分配/移动/等可以根据emplace
来实现。 SFINAE using can_store<T>
can ensure that only types the some
can actually store are assignable to it, avoiding needless exceptions. SFINAE使用
can_store<T>
可以确保只有some
类型可以实际存储的类型才可以分配给它,避免不必要的异常。
Currently, moving from some
destroys its contents instead of just moving from it. 当前,从
some
移动会破坏其内容,而不仅仅是移动它。 And a some
can be empty (they are "nulllable"). 并且
some
可以为空(它们是“不可占用的”)。
load_from
is a 'can-fail' copy constructor from another some
-- it returns false
on failure. load_from
是从另一个“能不能倒”的拷贝构造函数some
-它返回false
失败。 I could add a 'cannot-fail' from a smaller some
(even a copy/assignment operator) to complete it. 我可以从一小
some
(甚至是复制/分配运算符)中添加一个“无法失败”来完成它。
some_meta
is a manual virtual function table. some_meta
是一个手动虚拟功能表。 One exists per type T
you store in a some
of any size. 每种类型存在一个
T
你在商店里some
任意大小的。 It stores the type-erased operations on the type T
that some
wants to use (in this case, copy move and destroy), plus some data about the type (size, alignment and type identity). 它在
some
要使用的类型T
上存储了类型擦除的操作(在这种情况下,是复制移动和销毁),以及一些有关该类型的数据(大小,对齐和类型标识)。 It could be augmented with additional operations like comparison and serialization. 可以通过其他操作(例如比较和序列化)来增强它。 For binary operations, logic to handle "no matching type" has to be considered.
对于二进制操作,必须考虑处理“无匹配类型”的逻辑。 For stuff like serialization, I'd have it call the free function
serialize
and deserialize
on the T
. 对于诸如序列化之类的东西,我会让它在
T
上调用自由函数serialize
和deserialize
serialize
。 In both cases, we impose additional requirements on what some
can store (you can, with a bit of work, handle "maybe serialize", but that gets messy). 在这两种情况下,我们对
some
存储内容都施加了附加要求(您可以花some
工作来处理“也许序列化”,但这很麻烦)。
You could even imagine a system where you can store a set of operations to perform on the data (binary and unary) and pass said operations bundled in types passed to some. 您甚至可以想象一个系统,在其中可以存储一组对数据(二进制和一元数据)执行的操作,然后将捆绑在一起的所述操作传递给某些类型。 At this point, we are approaching
boost
's type erasure library, however. 此时,我们正在接近
boost
的类型擦除库。
namespace details {
template<std::size_t Size, std::size_t Align=0>
struct storage_helper {
using type = std::aligned_storage_t<Size, Align>;
enum { alignment = alignof(type), size = Size };
};
template<std::size_t Size>
struct storage_helper<Size, 0> {
using type = std::aligned_storage_t<Size>;
enum { alignment = alignof(type), size = Size };
};
template<std::size_t size, std::size_t align>
using storage_helper_t = typename storage_helper<size,align>::type;
template<class T>using type=T;
struct some_meta {
type<void(void*)>* destroy;
type<void(void* dest, void const* src)>* copy;
type<void(void* dest, void* src)>* move;
std::type_index type;
size_t size;
size_t align;
template<class T> static some_meta const* get() {
static const some_meta retval( create<T>() );
return &retval;
};
private:
template<class T> static some_meta create() {
return {
[](void* p){ ((T*)p)->~T(); },
[](void* out, void const* in){ new(out)T(*(T*)in); },
[](void* dest, void* src) { new(dest)T(std::move(*(T*)src)); },
typeid(T),
sizeof(T),
alignof(T)
};
}
};
}
template<class>struct emplace_as{};
template< std::size_t size, std::size_t Align=0 >
struct some {
enum { align = details::storage_helper<size, Align>::alignment };
using data_type = details::storage_helper_t<size, Align>;
template<size_t, size_t> friend struct some;
template<class T> struct can_store :
std::integral_constant< bool, ((align%alignof(T))==0) && sizeof(T) <= size) >
{};
template<size_t x, size_t a>
static bool can_fit( some<x,a> const& o ) {
if (x<=size && ((align%some<x,a>::align)==0)) return true; // should cause optimizations
if (!o.meta) return true;
if (o.meta->size > size) return false;
if (o.meta->align > align) return false;
return true;
}
private:
data_type data;
details::some_meta const* meta = nullptr;
public:
// true iif we are (exactly) a T
template<class T>
bool is() const {
return meta && (meta->type == typeid(T));
}
explicit operator bool()const { return meta!=nullptr; }
template<class T>
T* unsafe_get() { return reinterpret_cast<T*>(&data); }
template<class T>
T* get() { if (is<T>()) return unsafe_get<T>(); else return nullptr; }
void clear() { if (meta) meta->destroy(&data); meta = nullptr; }
template<class T, class... Args>
std::enable_if_t< can_store<T>{} >
emplace(Args&&...args) {
clear();
new(&data) T(std::forward<Args>(args)...);
meta = details::some_meta::get<T>();
}
some()=default;
some(some const& o) {
*this = o;
}
some(some const&&o):some(o){}
some(some&o):some(const_cast<some const&>(o)){}
some(some&& o) {
*this = std::move(o);
}
some& operator=(some const&o) {
if (this == &o) return *this;
clear();
if (o.meta) {
o.meta->copy( &data, &o.data );
meta=o.meta;
}
return *this;
}
some& operator=(some &&o) {
if (this == &o) return *this;
clear();
if (o.meta) {
o.meta->move( &data, &o.data );
meta=o.meta;
o.clear();
}
return *this;
}
some& operator=(some const&&o) { return *this=o; }
some& operator=(some &o) { return *this=const_cast<some const&>(o); }
// from non-some:
template<class T,class=std::enable_if_t<can_store<std::decay_t<T>>{}>>
some(T&& t){
emplace<std::decay_t<T>>(std::forward<T>(t));
}
template<class T, class...Args,class=std::enable_if_t<can_store<T>{}>>
some( emplace_as<T>, Args&&...args ){
emplace<T>(std::forward<Args>(args)...);
}
template<class T,class=std::enable_if_t<can_store<std::decay_t<T>>{}>>
some& operator=(T&&t){
emplace<std::decay_t<T>>(std::forward<T>(t));
return *this;
}
template<size_t x, size_t a>
bool load_from( some<x,a> const& o ) {
if ((void*)&o==this) return true;
if (!can_fit(o)) return false;
clear();
if (o.meta) {
o.meta->copy( &data, &o.data );
meta=o.meta;
}
return true;
}
template<size_t x, size_t a>
bool load_from( some<x,a> && o ) {
if ((void*)&o==this) return true;
if (!can_fit(o)) return false;
clear();
if (o.meta) {
o.meta->move( &data, &o.data );
meta=o.meta;
o.clear();
}
return true;
}
~some() { clear(); }
};
template<class T, class...Ts>
using some_that_fits = some< (std::max)({sizeof(T),sizeof(Ts)...}), (std::max)({alignof(T),alignof(Ts)...}) >;
the meta
object is a manually implemented virtual function table, basically. meta
对象基本上是一个手动实现的虚拟功能表。 It reduces the memory overhead of a given some
to one pointer (above its storage buffer). 它减少了给定内存开销
some
到一个指针(高于其贮存缓冲液)。
As demonstrated above, it is quite viable. 如上所述,这是非常可行的。
Note that create
returns a pointer to the same meta
for the same type T
, even if called more than once. 请注意,即使多次调用,
create
也会为相同类型T
返回指向相同meta
的指针。
I have exercised about half the code paths in my test above. 在上面的测试中,我已经练习了大约一半的代码路径。 The others probably have bugs.
其他人可能有错误。
some_that_fits
lets you pass a set of types, and it returns a some
type that fits those types. some_that_fits
允许您传递一组类型,并返回适合这些类型的some
类型。
No exceptions, other than those generated by the operations on the stored types by said stored types, are thrown. 除了由所述存储类型对存储类型的操作所生成的异常以外,不会引发任何异常。 When possible, I test at compile time to ensure types fit.
如果可能,我会在编译时进行测试以确保类型适合。
I could possibly add support for greater alignment, small storage types by starting them at an offset into my data? 我可以通过在数据偏移量处启动小存储类型来增加对更大对齐的支持?
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.