在这个简单示例中,为什么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

这里,Tsize_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 中也是如此。


以上是在这个简单示例中,为什么std::optional的赋值运算符在编译时上下文中不可用?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>