std::optional 和 "remove the move constructor from overload resolution" 是什么意思?

With std::optional, what does it mean to "remove the move constructor from overload resolution"?

我正在用 C++14 创建 std::optional 的实现。但是,我对如何指定移动构造函数感到有些困惑。这就是我所指的(强调我的):

The expression inside noexcept is equivalent to is_nothrow_move_constructible_v<T>. This constructor shall not participate in overload resolution unless is_move_constructible_v<T> is true. If is_trivially_move_constructible_v<T> is true, this constructor shall be a constexpr constructor.

从重载决策中删除移动构造函数是什么意思?删除和 SFINAE 似乎不适用于这种情况。

这意味着它不存在于 . In 你可以用 requires 子句做到这一点。

您可以使用默认和继承的基础等;如果您从 class 继承而没有移动构造函数,则您没有移动构造函数。不存在的事物不参与重载决议。

是的,SFINAE 不适用于构造函数,使用 强制编译器做正确的事情。

表示未定义,class不能移动构造。更有趣的问题是为什么需要它?

我不是 100% 确定我的答案是正确的。

TL;DR 如果存在 optional 的移动构造函数,则返回 std::optional<NonMoveable> 会产生编译器错误。另一方面,返回 NonMoveable 直接回退到复制构造函数。

首先,约束没有破坏任何东西。如果T不能移动构造,则构造函数无法实现。

其次,由于std::optional<std::optional<T>>问题,std::optional的所有方法都非常棘手,如果不采取适当的约束,很容易导致模棱两可的调用,optional(U&& value)确实容易受到此影响.

我认为,主要原因是因为我们希望 optional<T> 尽可能充当 T 并且我知道存在一种边缘情况,当存在 std::optional 的不可移动 T 的移动构造函数会导致不必要的编译器错误。巧合的是,它是从函数中按值返回 std::optional 的情况,我经常这样做。

对于T类型的变量x,函数T foo()中的return x如果可访问则调用移动构造函数,如果不可访问则复制。

采用这些简单的定义:

#include <utility>
struct CopyOnly {
    CopyOnly() = default;
    CopyOnly(const CopyOnly &) = default;
    CopyOnly(CopyOnly &&) = delete;

    CopyOnly &operator=(const CopyOnly &) = default;
    CopyOnly &operator=(CopyOnly &&) = delete;
    ~CopyOnly() = default;
};

template <typename T> struct Opt {
    Opt() = default;
    Opt(const Opt &other) : m_value(other.m_value) {}
    Opt(Opt &&other) : m_value(std::move(other.m_value)) {
        // Ordinary move ctor.
        // Same as =default, just writing for clarity.
    }
    // Ignore how `T` is actually stored to be "optional".
    T m_value;
};

和这个例子

template <typename T> T foo(const T &t) {
    auto x = t;
    return x;
}

int main() {

    Opt<int> opt_int;
    CopyOnly copy;
    Opt<CopyOnly> opt_copy;
    
    foo(opt_int);//#1
    foo(copy);//#2
    foo(opt_copy);//#3
}

return x:

  1. 调用移动构造函数,因为 opt_int 可以移动。
  2. 调用复制构造函数作为备用,因为它无法移动。
  3. 编译器错误,因为 Opt<CopyOnly> 具有可访问的移动构造函数,因此它被选中,但由于 m_value(std::move(other.m_value)) 试图显式调用已删除的移动构造函数,其实例化导致错误。

如果禁用移动构造函数,则选择复制构造函数,代码与#2 相同。

使用继承。定义一个基础 class 模板并将其专门用于不同的 T 可构造性特征,根据需要标记移动构造函数 delete

template<bool>
struct enable_move {};

template<>
struct enable_move<true> {
    constexpr enable_move() noexcept = default;
    constexpr enable_move(enable_move&&) noexcept = default;
};

template<>
struct enable_move<false> {
    constexpr enable_move() noexcept = default;
    constexpr enable_move(enable_move&&) noexcept = delete;
};

template<typename T>
struct optional : private enable_move<is_move_constructible<T>::value> {
    // . . .
};

将其与具有类似要求的其他特殊成员组合时会变得复杂,例如 copy/move 构造函数和 copy/move 赋值运算符。

出于这个原因,例如在 libstdc++ 中,optional derives from _Enable_copy_move (see 1, 2),它同时处理所有 4 个特殊成员。