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&lt;T&gt; 是一个字面量类型 可破坏的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&lt;T&gt; 成为文字类型是不可能的: 析构函数不能是微不足道的,因为它必须执行一个操作 在概念上可以描述为:

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

仍然可以使T 的析构函数变得微不足道 自己提供一个微不足道的析构函数,我们知道一个有效的 使用编译时接口实现这样的optional&lt;T&gt; — 除了复制构造函数和移动构造函数——是可能的。 因此,我们建议对于可轻易破坏的T's all optional&lt;T&gt; 的构造函数,除了移动和复制构造函数, 以及观察者函数是 constexpr。参考草图 本提案中提供了实施。

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

【讨论】:

【参考方案3】:

如何在与赋值相同的上下文中使用 std::..::optional 在 constexpr 函数内部?

std::optional 用于保存可能存在或不存在的值。 std::optional's assignment 的问题是它必须销毁旧状态(调用包含对象的析构函数)(如果有的话)。而且你不能有constexpr 析构函数。

当然,平凡和整数类型不应该有问题,但我认为概括是为了让事情保持理智。但是,可以为琐碎的类型分配 constexpr。希望它会得到纠正。在此之前,您可以扮演自己的角色。 :-)

即使你认为std::optional的构造函数是constexpr,实际上是选择性的constexpr(取决于选择的对象构造函数是不是)。 可以找到它的提案here

【讨论】:

【参考方案4】:

不幸的是,constexprstd::optional 的支持有点初级;启用constexpr 的成员函数只是(空且已启用的)构造函数、析构函数和一些观察者,因此您无法更改可选项的已启用状态。

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

解决方法是使赋值运算符有条件地 constexpr 依赖于包含的类型是否微不足道 (std::is_trivial_v&lt;T&gt;)。

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

【讨论】:

以上是关于constexpr 函数内的 std::experimental::optional的主要内容,如果未能解决你的问题,请参考以下文章

使用 constexpr 成员函数初始化 constexpr 成员变量

为啥 NVCC 对 constexpr 比非 constexpr 主机函数更严格?

为啥这个 constexpr 静态成员函数在调用时不被视为 constexpr?

为啥**不**将函数声明为`constexpr`?

以 constexpr 和不带 constexpr 的形式运行函数

在非 constexpr 函数上添加的 constexpr 限定符不会触发任何警告