libcxx 中的可选 std::nullopt_t 实现

optional std::nullopt_t implementation in libcxx

Clang 以这种方式实现 std::nullopt_t

struct nullopt_t
{
    explicit constexpr nullopt_t(int) noexcept {}
};

constexpr nullopt_t nullopt{0}; 

为什么不简单:

struct nullopt_t{};

constexpr nullopt_t nullopt{};

?

根据cppreference

std::nullopt_t must be a LiteralType and cannot have a default constructor. It must have a constexpr constructor that takes some implementation-defined literal type.

... 所以如果 Clang 按照你的建议实现了 nullopt_t,它就不能满足要求。现在,如果您想知道为什么存在这些要求(另一个问题),答案是:

nullopt_t is not DefaultConstructible to support both op = {}; and op = nullopt; as the syntax for disengaging an optional object.

std::nullopt_t 所述:

Notes

nullopt_t is not DefaultConstructible to support both op = {}; and op = nullopt; as the syntax for disengaging an optional object.

现在用你的实现测试这个:

struct nullopt_t { };
template <typename T>
struct optional {
  optional &operator=(nullopt_t);
  optional &operator=(const optional &);
  optional &operator=(optional &&);
  template <typename U>
  optional &operator=(U &&);
};
int main() {
  optional<int> oi;
  oi = {};
}

这失败了,因为对 operator= 的调用不明确。可能是尝试调用 operator=(nullopt_t),也可能是尝试调用 operator=(optional &&),并且没有语言规则来解决这种歧义。

因此,除非语言规则发生变化,或者 oi = {}; 不再需要有效,否则 nullopt_toptional 不需要是默认构造的,并且nullopt_t 是合乎逻辑的选择。

其他人已经解释了为什么 struct nullopt_t {}; 不够。但是至少有两种替代解决方案仍然比目前在 libc++ 和 libstdc++ 中的解决方案更简单。

(这是一些测试代码:http://melpon.org/wandbox/permlink/HnFXRjLyqi4s4ikv

以下是备选方案,按偏执狂的递增顺序排列:

选项 1(据我所知没有人)

struct nullopt_t { constexpr explicit nullopt_t() {} };
constexpr nullopt_t nullopt{};

选项 2 (libc++ )

struct nullopt_t { constexpr explicit nullopt_t(int) {} };
constexpr nullopt_t nullopt{0};

选项 3 (libstdc++ )

struct nullopt_t {
    enum class secret { tag };
    explicit constexpr nullopt_t(secret) { }
};
constexpr nullopt_t nullopt{nullopt_t::secret::tag};

选项 4(libc++ <可选>)

struct nullopt_t {
    struct secret_tag { explicit secret_tag() = default; };
    constexpr explicit nullopt_t(secret_tag, secret_tag) noexcept {}
};
constexpr nullopt_t nullopt{nullopt_t::secret_tag{}, nullopt_t::secret_tag{}};

这些备选方案中的任何一个(据我所知,即使是选项 1)也足以使赋值 o = {} 明确无误。我不知道为什么 libc++ 和 libstdc++ 都超越了该解决方案 — 并注意到 libc++ 甚至创建了一个 双参数 构造函数!

如果有人知道 libc++ 偏执狂增加的原因,我很想听听;请创建一个答案 and/or 在下面发表评论。