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_t
或 optional
不需要是默认构造的,并且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 在下面发表评论。
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 bothop = {};
andop = nullopt;
as the syntax for disengaging an optional object.
如 std::nullopt_t 所述:
Notes
nullopt_t
is notDefaultConstructible
to support bothop = {};
andop = 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_t
或 optional
不需要是默认构造的,并且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 在下面发表评论。