右值引用是否满足 Cpp17MoveConstructible 要求?
为此,在表达式
T u = rv;
中,u
需要“等价”于 rv
。如果 T
是右值引用(例如 int&&
),那么在 int&& u = 0;
中,u
需要“等效”于 0
。然而,引用是否等同于一个值,还是仅仅引用一个等同的对象?
std::swap<int&&>
应该如何表现。
is_move_constructible_v<int&&>
和 is_move_assignable_v<int&&>
为真,因此满足 std::swap
的约束并且可以实例化。
但是,如果 swap<int&&>
不满足 Cpp17MoveConstructible,则
int&&
的效果未定义。
事实上,所有主要实现(libc++、libstdc++、MSVC STL)都按照以下方式实现
std::swap(a, b)
:
T t = std::move(a);
a = std::move(b);
b = std::move(t);
如果
T
是右值引用,则 T t = _MOVE(a)
不会创建移动构造的副本,而是将一个右值引用绑定到另一个右值引用。
对于 T = int&&
,效果相当于:
a = a;
b = a;
我们对此有何看法?
swap
的前提条件不满足所以可以这样。嗯,是哪一个?
在我看来,右值引用满足 Cpp17MoveConstructible 和 Cpp17MoveAssignable,因此满足
std::swap
的先决条件。
但是,
std::swap<int&&>(a, b)
不会交换两个 int
变量。完整的示例是here:
#include <type_traits>
#include <cstdio>
template <class T>
requires std::is_move_constructible_v<T> && std::is_move_assignable_v<T>
constexpr void swap(T& a, T& b)
noexcept(std::is_nothrow_move_constructible_v<T> && std::is_nothrow_move_assignable_v<T>)
{
T tmp = std::move(a);
a = std::move(b);
b = std::move(tmp);
}
int main()
{
int a = 1, b = 2;
swap<int&&>(a, b);
std::printf("a = %d, b = %d.\n", a, b);
}
原因是,
swap
的第一行应该将a
的值存储在临时变量中,但T
是int&&
,它不是值类型。所以,这个初始化不会移动任何东西,它只是创建一个别名。
我认为,您使用
std::swap
是一种非常罕见的方法。通常模板参数是推导出来的,因此它永远不是引用类型。
不过,我们必须承认,标准中的Effects并没有得到满足,因此这是所有实现中的一个错误。虽然,不是太严重。他们应该使用
std::decay_t<T>
或 auto
作为 tmp
的类型。