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 似乎不适用于这种情况。
这意味着它不存在于 c++14. In c++20 你可以用 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
:
- 调用移动构造函数,因为
opt_int
可以移动。
- 调用复制构造函数作为备用,因为它无法移动。
- 编译器错误,因为
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 个特殊成员。
我正在用 C++14 创建 std::optional
的实现。但是,我对如何指定移动构造函数感到有些困惑。这就是我所指的(强调我的):
The expression inside
noexcept
is equivalent tois_nothrow_move_constructible_v<T>
. This constructor shall not participate in overload resolution unlessis_move_constructible_v<T>
istrue
. Ifis_trivially_move_constructible_v<T>
istrue
, this constructor shall be a constexpr constructor.
从重载决策中删除移动构造函数是什么意思?删除和 SFINAE 似乎不适用于这种情况。
这意味着它不存在于 c++14. In c++20 你可以用 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
:
- 调用移动构造函数,因为
opt_int
可以移动。 - 调用复制构造函数作为备用,因为它无法移动。
- 编译器错误,因为
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 个特殊成员。