在这个简单示例中,为什么std::optional的赋值运算符在编译时上下文中不可用?
摆弄编译器资源管理器(以及在 std::optional 上阅读 cppref.com)半小时后,我放弃了。除了我不明白为什么这段代码不能编译之外,没有什么可说的。有人请解释这一点,如果有的话,也许可以告诉我一个解决方法?std::optional我在这里使用的所有成员函数都是constexpr,并且确实应该在编译时可计算,因为这里的可选类型 - size_t- 是原始标量类型。
#include <type_traits>
#include <optional>
template <typename T>
[[nodiscard]] constexpr std::optional<size_t> index_for_type() noexcept
{
std::optional<size_t> index;
if constexpr (std::is_same_v<T, int>)
index = 1;
else if constexpr (std::is_same_v<T, void>)
index = 0;
return index;
}
static_assert(index_for_type<int>().has_value());
https://godbolt.org/z/YKh5qT4aP
回答
让我们简单地了解一下:
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。
这里,T是size_t(它是特化的模板参数optional),U是参数类型。在原始情况下,index = 1, Uisint使第二个项目符号保持不变(int实际上,不是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 中也是如此。