为什么在std :: optional的某些实现中会有一个伪联合成员?

r3m*_*n0x 28 c++ optional unions c++17

libstdc ++(GNU)和libc ++(LLVM)都std::optional使用联合实现值存储,并且它们都包含一个虚拟成员。

GNU实现:

using _Stored_type = remove_const_t<_Tp>;
struct _Empty_byte { };
union {
    _Empty_byte _M_empty;
    _Stored_type _M_payload;
};
Run Code Online (Sandbox Code Playgroud)

LLVM实施:

union
{
    char __null_state_;
    value_type __val_;
};
Run Code Online (Sandbox Code Playgroud)

我的问题是:我们为什么需要这些_M_empty/ __null_state_会员?单成员工会有什么问题吗?

Gui*_*cot 28

当试图成为默认的可构造对象并与constexpr兼容时,单成员联合会产生各种问题。

考虑以下代码:

struct nontrivial {
    constexpr nontrivial(int o) : u{o} {}
    int u;
};

union storage {
    nontrivial nt;
};

struct optional {
    storage s;
};

constexpr auto run() -> int {
    optional o;
    return o.s.nt.u;
}

int main() {
    constexpr int t = run();
}
Run Code Online (Sandbox Code Playgroud)

这是错误的形式,因为optional具有删除的构造函数。

然后,一个简单的解决方法是添加一个不初始化任何联合成员的构造函数:

union storage {
    constexpr storage() {} // standard says no
    nontrivial nt;
};
Run Code Online (Sandbox Code Playgroud)

但这是行不通的。Constexpr工会必须至少有一个活跃成员。它不能是一个空的联合。若要解决此限制,添加了一个虚拟成员。这使得std::optional在constexpr上下文中可用。

(感谢@Barry!)来自[dcl.constexpr] / 4(强调我的):

函数体不是= delete的constexpr构造函数的定义还应满足以下要求:

  • 如果该类是具有变体成员([class.union])的联合,则应精确地初始化其中一个成员;
  • 如果该类是类联合的类,但不是联合,则对于其每个具有变体成员的匿名联合成员,应准确初始化其中一个成员;

  • 对于非委托的构造函数,选择用来初始化非静态数据成员和基类子对象的每个构造函数均应为constexpr构造函数;

  • 对于委托的构造函数,目标构造函数应为constexpr构造函数。

  • 对于那些寻求标准报价的人:http://eel.is/c++draft/dcl.dcl#dcl.constexpr-4.1 (7认同)