std::experimental::optional constexpr 函数内部

std::experimental::optional inside constexpr function

我想在我的 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;
}

我得到错误:调用非 constexpr 函数 - 所以赋值是不可能的,因为这个操作不能是 constexpr (Example)。

但是如果我实现我自己的(非常丑陋,just for example)可选的 class,它会起作用,因为我没有实现赋值 operator/constructor 显式。

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 函数内部赋值的相同上下文中使用?

基本上,你不能。您的简单实现的问题在于它需要 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

How could I use std::..::optional in the same context with assignment inside constexpr functions?

std::optional is meant to hold a value that may or may not be present. The problem with std::optional's assignment是如果有的话必须销毁旧状态(调用包含对象的析构函数)。而且你不能有一个 constexpr 析构函数。

当然,Trivial 和整数类型应该没有问题,但我认为概括是为了让事情保持理智。但是,可以为普通类型进行赋值 constexpr。希望它会得到纠正。在此之前,您可以发挥自己的作用。 :-)

std::optional的构造函数你以为是constexpr,其实是选择性的constexpr(要看被选对象构造函数是不是)。 其提案可见here

不幸的是,constexprstd::optional 的支持有些简陋; constexpr-启用的成员函数只是(空的和参与的)构造函数、析构函数和一些观察者,所以你不能改变可选的参与状态。

这是因为如果不使用 placement new 和包含对象的就地销毁,就无法实现非平凡可复制类型的赋值,这在 constexpr 上下文中是非法的。目前对于复制和移动构造函数也是如此,尽管这可能会因保证复制省略而改变,但无论如何标准将这些特殊成员函数标记为非 constexpr,因此您不能在 constexpr 中使用它们上下文。

解决方法是使赋值运算符有条件地 constexpr 取决于包含的类型是否微不足道 (std::is_trivial_v<T>)。

有人讨论这个问题at the reference implementation;尽管将 constexpr 琐碎的可选值赋值到下一版本的标准中可能为时已晚,但没有什么能阻止您编写自己的代码(例如通过复制和修复参考实现)。

这是不可能的,如 n3527 中所述:

Making optional a literal type

We propose that optional<T> be a literal type for trivially destructible T's.

constexpr optional<int> oi{5};
static_assert(oi, "");            // ok
static_assert(oi != nullopt, ""); // ok
static_assert(oi == oi, "");      // ok
int array[*oi];                   // ok: array of size 5 

Making optional<T> a literal-type in general is impossible: the destructor cannot be trivial because it has to execute an operation that can be conceptually described as:

~optional() {
  if (is_engaged()) destroy_contained_value();
}

It is still possible to make the destructor trivial for T's which provide a trivial destructor themselves, and we know an efficient implementation of such optional<T> with compile-time interface — except for copy constructor and move constructor — is possible. Therefore we propose that for trivially destructible T's all optional<T>'s constructors, except for move and copy constructors, as well as observer functions are constexpr. The sketch of reference implementation is provided in this proposal.

换句话说,即使您将其标记为 constexpr,也无法为 r 赋值。您必须在同一行中对其进行初始化。