我正在尝试创建一个静态容器,该容器具有基于堆栈的内存,并且可以容纳N个T实例。非常相似std::vector
我希望当前未使用的内存不包含T的初始化项。通常可以通过放置来解决新的,但是在constexpr中无法使用。
使用工会 我发现了一个技巧,您可以为此使用联合,如下所示:
template <typename value_type>
union container_storage_type
{
struct empty{};
constexpr container_storage_type(): uninitialized{}{}
constexpr container_storage_type(value_type v): value(v){}
constexpr void set(value_type v)
{
*this = literal_container_storage_type{v};
}
empty uninitialized;
value_type value;
};
这使您可以通过设置empty
成员来存储未初始化的项目,并且可以解决constexpr中所有成员都必须初始化的限制。
现在这种方法的问题在于,如果value_type
是实现operator=
的类型,则rule for unions says:
如果联合包含具有非平凡特殊成员函数(复制/移动构造函数,复制/移动分配或析构函数)的非静态数据成员,则该函数默认在联合中被删除并需要定义由程序员明确表示。
这意味着要能够使用此技巧,我也需要在联合体中实现operator=
,但是看起来如何?
constexpr container_storage_type& operator=(const container_storage_type& other)
{
value = other.value; //ATTEMPT #1
//*this = container_storage_type(other.value);ATTEMPT #2
return *this;
}
尝试#1:这似乎是不可能的,因为编译器抱怨在常量表达式中根本不允许更改联合的活动成员。
尝试2:此方法在上一个代码段的set()
方法中有效,因为它本身不会更改活动成员,但会重新分配整个联合。这个技巧似乎无法在赋值运算符中使用,但是因为这会导致无限递归...
我在这里错过了什么吗,或者这真的是将联合作为constexpr中的新放置替代品的死胡同吗?
我完全想念的还有其他替代品吗?
https://godbolt.org/z/km0nTY说明问题的代码
答案 0 :(得分:5)
在C ++ 17中,您不能这样做。
关于current restrictions不能在常量表达式中执行的操作的内容包括:
一个赋值表达式([expr.ass])或一个赋值运算符([class.copy.assign])的调用,这些赋值运算符将更改联合的活动成员;
一个 new-expression ;
真的没有办法解决。
在C ++ 20中,您将能够,但可能不是您想的那样。由于P0784的出现,后一种限制在C ++ 20中将得到放宽,例如:
- new-expression (8.3.4),除非所选分配功能是可替换的全局分配功能(21.6.2.1,21.6.2.2);
也就是说,new T
可以正常使用,但仍然不允许new (ptr) T
。使std::vector
constexpr
友好的一部分,我们需要能够管理“原始”内存-但是我们仍然无法真正管理真正的 raw 内存。一切仍然必须输入。处理原始字节将无法正常工作。
但是std::allocator
不会完全处理原始字节。 allocate(n)
给您一个T*
,而construct
则以T*
作为位置和一堆参数,并在该位置创建一个新对象。您可能会想知道这与新的刊登位置有什么不同-唯一的区别是坚持std::allocator
,我们留在T*
的土地上-但新的刊登位置使用{{1} }。这种区别非常关键。
不幸的是,这会产生有趣的结果,即您的void*
版本“分配”了内存(但是它分配了编译器内存,必要时会将其提升为静态存储-这样就可以了)-但是您的纯运行时版本肯定不希望分配内存,实际上整个要点是它不需要。为此,您将必须使用constexpr
在恒定评估时间的分配与运行时间的非分配之间切换。公认这不是很漂亮,但是应该可以。
答案 1 :(得分:-2)
您的存储空间可能看起来像这样:
// For trivial objects
using data_t = const array<remove_const_t<T>, Capacity>>;
alignas(alignof(T)) data_t data_{};
// For non-trivial objects
alignas(alignof(T)) aligned_storage_t<T> data_[Capacity]{};
这将允许您创建{em> non -const
对象的const
数组。然后,构造对象将如下所示:
// Not real code, for trivial objects
data_[idx] = T(forward<Args>(args)...);
// For non-trivial objects
new (end()) T(forward<Args>(args)...);
在此处必须强制放置新的位置。您将能够在编译时拥有该存储,但不能在编译时为非平凡的对象构造。
您还需要考虑您的容器是否为零大小,等等。我建议您查看固定大小向量的现有实现,甚至还有一些关于constexpr
固定大小向量的建议,例如p0843r1。