为什么没有“变体”任何在boost或标准? [英] Why no "variant" any in boost or Standard?

查看:112
本文介绍了为什么没有“变体”任何在boost或标准?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

任何超过 variant 的一个优点是,不需要指定所有类型,包含。我注意到,随着变体可能包含的类型数量的增长,人们趋向于切换到任何有些点,因为他们根本不跟踪所有的类型了。我认为任何变体之间的混合是可能的。可以存储的任何 c $ 占位符(通过 new $ c> in aligned_storage ,其大小在 constexpr 函数或模板元函数中计算,类型,可能最终被存储。另一方面,用户不需要指定任何可能包含的所有类型。如果用户尝试在其中存储大于 aligned_storage 的内容,则 any



这样的 variant_any 类是否存在?

解决方案

这是一个基本的一些



T copy / assign / move / etc可以用 emplace 。使用 can_store< T> 的SFINAE可以确保只有一些可以实际存储的类型才可分配给它, 。



目前,从移动销毁其内容,而不是仅仅从其移动。和一些可以为空(它们是nulllable)。



load_from 是来自另一个一些的可以失败拷贝构造函数 - 它返回 false 失败。我可以从更小的添加一个 c>(即使是复制/赋值运算符)来完成它。



some_meta 是手动虚拟函数表。一个存在每个 T 你存储在一个一些任何大小。它在类型 T 上存储类型删除的操作,有些想使用(在这种情况下,销毁),以及一些关于类型(大小,对齐和类型标识)的数据。它可以通过比较和序列化等附加操作来扩充。对于二进制操作,必须考虑处理无匹配类型的逻辑。对于类似序列化的东西,我可以在 serialize deserialize > T 。在这两种情况下,我们对一些可以存储的额外要求(你可以,有点工作,处理可能序列化,但是变得混乱)。

你甚至可以想象一个系统,你可以存储一组对数据执行的操作(二进制和一进制),并将传递给某些类型的绑定类型的操作传递给它们。此时,我们正在接近 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) },
[](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>朋友struct一些
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& ))return true; //应该导致优化
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我们(确切地)是一个T
模板< class T>
bool is()const {
return meta&& (meta-> type == typeid(T));
}

显式运算符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& }
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&运算符=(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&
}
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)...})>对象是一个手动实现的虚拟对象。



<函数表,基本上。它减少了给定一些到一个指针(高于其存储缓冲区)的内存开销。



实例



如上所示



请注意, create 返回一个指向同一个 code>对于同一类型 T ,即使被多次调用。



一半的代码路径在我上面的测试。



some_that_fits 可以传递一组类型,并返回一些类型。



除了通过所述存储类型对存储类型的操作生成的异常,被抛出。如果可能,我在编译时测试,以确保类型适合。



我可能添加支持更大的对齐,小的存储类型通过启动它们偏移到我的数据?


One advantage of any over variant is, that one does not need to specify all types, that it may contain. 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. I think a hybrid between any and variant is possible. 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. The user, on the other hand, would not need to specify all the types, that an any might contain. The any could also throw at any time, if the user would try to store something larger than the aligned_storage in there.

Does such a "variant_any" class exist? Is there some inherent problem with the idea?

解决方案

Here is a basic some.

The T copy/assign/move/etc can be implemented in terms of emplace. SFINAE using can_store<T> can ensure that only types the some can actually store are assignable to it, avoiding needless exceptions.

Currently, moving from some destroys its contents instead of just moving from it. And a some can be empty (they are "nulllable").

load_from is a 'can-fail' copy constructor from another some -- it returns false on failure. I could add a 'cannot-fail' from a smaller some (even a copy/assignment operator) to complete it.

some_meta is a manual virtual function table. One exists per type T you store in a some of any size. 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). 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. 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).

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.

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. It reduces the memory overhead of a given some to one pointer (above its storage buffer).

live example

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.

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.

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?

这篇关于为什么没有“变体”任何在boost或标准?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆