为什么 std::optional 的赋值运算符在这个简单示例的编译时上下文中不可用?
Why is std::optional's assignment operator not usable in compile-time context in this simple example?
在 Compiler Explorer(以及 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());
让我们简单一点:
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;
,您在 assignment operators 中尝试调用的候选人是 #4:
template< class U = T >
optional& operator=( U&& value );
请注意,这个候选并不是最初在 C++20 中制作的 constexpr
,它是最近的 DR (P2231R1)。 libstdc++ 尚未实现此更改,这就是您的示例无法编译的原因。到目前为止,它是完全正确的 C++20 代码。图书馆还没有完全跟上。
Marek's 建议有效的原因:
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
有这个约束:
and at least one of the following is true:
T
is not a scalar type;
std::decay_t<U>
is not T
.
这里,T
是size_t
(它是optional
特化的模板参数),U
是参数类型。在原始情况下,index = 1
、U
是 int
,这使得第二个项目符号成立(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 中也是如此。
在 Compiler Explorer(以及 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());
让我们简单一点:
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;
,您在 assignment operators 中尝试调用的候选人是 #4:
template< class U = T >
optional& operator=( U&& value );
请注意,这个候选并不是最初在 C++20 中制作的 constexpr
,它是最近的 DR (P2231R1)。 libstdc++ 尚未实现此更改,这就是您的示例无法编译的原因。到目前为止,它是完全正确的 C++20 代码。图书馆还没有完全跟上。
Marek's 建议有效的原因:
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
有这个约束:
and at least one of the following is true:
T
is not a scalar type;std::decay_t<U>
is notT
.
这里,T
是size_t
(它是optional
特化的模板参数),U
是参数类型。在原始情况下,index = 1
、U
是 int
,这使得第二个项目符号成立(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 中也是如此。