2

在使用 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());

https://godbolt.org/z/YKh5qT4aP

4

1 回答 1

6

让我们简单地说一下:

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 中也是如此。

于 2021-07-02T21:37:07.417 回答