constexpr 函数内的 std::experimental::optional
Posted
技术标签:
【中文标题】constexpr 函数内的 std::experimental::optional【英文标题】:std::experimental::optional inside constexpr function 【发布时间】:2016-06-06 08:49:59 【问题描述】:我想在我的 constexpr 函数中使用可选习语来轻松阐明变量是否已设置。
我对 std::experimental::optional 的尝试:
constexpr bool call()
std::experimental::optional<bool> r;
r = true; // Error
// Similar error with:
// r = std::experimental::optional<bool>(true);
if (!r)
return false;
return *r;
我得到错误:call to non-constexpr function - 所以赋值是不可能的,因为这个操作不能是 constexpr (Example)。
但如果我实现自己的(非常丑陋,just for example)可选类,它就可以工作,因为我没有显式地实现赋值运算符/构造函数。
template<typename T>
struct optional
bool m_Set;
T m_Data;
constexpr optional() :
m_Set(false), m_Data
constexpr optional(T p_Data) :
m_Set(true), m_Data(p_Data)
explicit constexpr operator bool()
return m_Set;
constexpr T operator *()
return m_Data;
;
如何在相同的上下文中使用 std::..::optional 并在 constexpr 函数中进行赋值?
【问题讨论】:
【参考方案1】:基本上,你不能。您的简单实现的问题在于它要求 T
是默认可构造的 - 如果不是这种情况,这将不起作用。
为了解决这个问题,大多数实现使用union
或一些(适当对齐的)可以容纳T
的存储。如果你在构造函数中传递了一个T
,那么一切都很好,你可以直接初始化它(因此它将是constexpr
)。然而,这里的权衡是当调用operator=
时,复制值可能需要一个placement-new 调用,它不能是constexpr
。
例如,来自 LLVM:
template <class _Up,
class = typename enable_if
<
is_same<typename remove_reference<_Up>::type, value_type>::value &&
is_constructible<value_type, _Up>::value &&
is_assignable<value_type&, _Up>::value
>::type
>
_LIBCPP_INLINE_VISIBILITY
optional&
operator=(_Up&& __v)
if (this->__engaged_)
this->__val_ = _VSTD::forward<_Up>(__v);
else
// Problem line is below - not engaged -> need to call
// placement new with the value passed in.
::new(_VSTD::addressof(this->__val_)) value_type(_VSTD::forward<_Up>(__v));
this->__engaged_ = true;
return *this;
至于为什么placement new不是constexpr
,见here。
【讨论】:
【参考方案2】:这是不可能的,如n3527 中所述:
使
optional
成为文字类型我们建议
optional<T>
是一个字面量类型 可破坏的T。constexpr optional<int> oi5; static_assert(oi, ""); // ok static_assert(oi != nullopt, ""); // ok static_assert(oi == oi, ""); // ok int array[*oi]; // ok: array of size 5
一般来说,使
optional<T>
成为文字类型是不可能的: 析构函数不能是微不足道的,因为它必须执行一个操作 在概念上可以描述为:~optional() if (is_engaged()) destroy_contained_value();
仍然可以使
T
的析构函数变得微不足道 自己提供一个微不足道的析构函数,我们知道一个有效的 使用编译时接口实现这样的optional<T>
— 除了复制构造函数和移动构造函数——是可能的。 因此,我们建议对于可轻易破坏的T
's alloptional<T>
的构造函数,除了移动和复制构造函数, 以及观察者函数是 constexpr。参考草图 本提案中提供了实施。
换句话说,即使您将其标记为 constexpr,也无法为 r
分配值。您必须在同一行中对其进行初始化。
【讨论】:
【参考方案3】:如何在与赋值相同的上下文中使用 std::..::optional 在 constexpr 函数内部?
std::optional
用于保存可能存在或不存在的值。 std::optional
's assignment 的问题是它必须销毁旧状态(调用包含对象的析构函数)(如果有的话)。而且你不能有constexpr
析构函数。
当然,平凡和整数类型不应该有问题,但我认为概括是为了让事情保持理智。但是,可以为琐碎的类型分配 constexpr
。希望它会得到纠正。在此之前,您可以扮演自己的角色。 :-)
即使你认为std::optional
的构造函数是constexpr
,实际上是选择性的constexpr
(取决于选择的对象构造函数是不是)。 可以找到它的提案here
【讨论】:
【参考方案4】:不幸的是,constexpr
对std::optional
的支持有点初级;启用constexpr
的成员函数只是(空且已启用的)构造函数、析构函数和一些观察者,因此您无法更改可选项的已启用状态。
这是因为如果不使用放置新和就地销毁包含的对象,就无法实现非平凡可复制类型的赋值,这在constexpr
上下文中是非法的。当前同样适用于复制和移动构造函数,尽管这可能会随着保证复制省略而改变,但无论如何标准将这些特殊成员函数标记为非constexpr
,因此您不能在constexpr
上下文中使用它们。
解决方法是使赋值运算符有条件地 constexpr
依赖于包含的类型是否微不足道 (std::is_trivial_v<T>
)。
关于这个问题有一些讨论at the reference implementation;尽管将琐碎可选项的constexpr
分配到标准的下一版本中可能为时已晚,但没有什么阻止您编写自己的(例如,通过复制和修复参考实现)。
【讨论】:
以上是关于constexpr 函数内的 std::experimental::optional的主要内容,如果未能解决你的问题,请参考以下文章
使用 constexpr 成员函数初始化 constexpr 成员变量
为啥 NVCC 对 constexpr 比非 constexpr 主机函数更严格?
为啥这个 constexpr 静态成员函数在调用时不被视为 constexpr?