让我们简单地说一下:
constexpr std::optional<size_t> index_for_type() noexcept
{
std::optional<size_t> index;
index = 1;
return index;
}
static_assert(index_for_type().has_value());
对于index = 1;您尝试在赋值运算符中调用的候选人是#4:
template< class U = T >
optional& operator=( U&& value );
请注意,这个候选者最初不是用constexprC++20 制作的,它是最近的 DR ( P2231R1 )。libstdc++ 尚未实现此更改,这就是您的示例无法编译的原因。到目前为止,它是完全正确的 C++20 代码。图书馆还没有完全赶上。
Marek 的建议有效的原因:
constexpr std::optional<size_t> index_for_type() noexcept
{
std::optional<size_t> index;
index = size_t{1};
return index;
}
static_assert(index_for_type().has_value());
是因为它没有调用赋值运算符#4(否则会因为同样的原因继续不起作用,只是还没有constexpr在这个实现中),而是切换到调用运算符#3(即constexpr):
constexpr optional& operator=( optional&& other ) noexcept(/* see below */);
为什么是这个?因为#4有这个约束:
并且以下至少一项是正确的:
T不是标量类型;
std::decay_t<U>不是T。
在这里,Tis size_t(它是特化的模板参数optional)并且U是参数类型。在最初的情况下,index = 1, Uisint使第二个项目符号保持不变(int实际上是 not size_t),因此这个赋值运算符是有效的。但是当我们将其更改为 时index = size_t{1},现在U变为size_t,因此第二个项目符号也是错误的,并且我们失去了这个赋值运算符作为候选者。
这使得复制作业和移动作业成为候选,后者更好。移动分配在 constexpr这个实现中,所以它可以工作。
当然,更好的实现仍然是避免分配,只是:
constexpr std::optional<size_t> index_for_type() noexcept
{
return 1;
}
static_assert(index_for_type().has_value());
或者,在原始函数中:
template <typename T>
[[nodiscard]] constexpr std::optional<size_t> index_for_type() noexcept
{
if constexpr (std::is_same_v<T, int>) {
return 1;
} else if constexpr (std::is_same_v<T, void>) {
return 0;
} else {
return std::nullopt;
}
}
这工作得很好,即使在 C++17 中也是如此。