让我们简单地说一下:
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 );
请注意,这个候选者最初不是用constexpr
C++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
。
在这里,T
is size_t
(它是特化的模板参数optional
)并且U
是参数类型。在最初的情况下,index = 1
, U
isint
使第二个项目符号保持不变(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 中也是如此。