为什么 std::tuple 有一个 const tuple<UTypes...>&& 构造函数?

问题描述 投票:0回答:1

在 cppreference 站点上检查

std::tuple
的构造函数时,我遇到了下面定义的移动构造函数,该构造函数在 C++23 中可用。

template< class... UTypes >
constexpr tuple( const tuple<UTypes...>&& other )

这里,

constexpr
表示如果可能的话,可以在编译时处理构造函数。然而,参数是
const
,这不是让移动语义变得毫无意义,还是我完全误解了?

c++ constructor move-semantics stdtuple c++23
1个回答
0
投票

std::tuple
(或
std::pair
)是标准库中的基本构建块之一,我们可以使用它来保存值、引用甚至它们的混合的元组。也就是说,
tuple
可以表现得像值类型或引用类型。如果我们只关心值元组,那么您的感觉有些正确:
const tuple<Us...>&&
重载基本上没有意义。然而,当我们引入引用元组时,事情会变得有点复杂。

正如

int&
可以绑定到
int
类型的左值一样,很自然地假设
tuple<int&>
可以从
tuple<int>
类型的左值(即
tuple<int>&
)初始化。此外,由于引用被烘焙到
tuple
的类型中,因此从
tuple<int&>
的左值(即
tuple<int&>
)初始化
tuple<int&>&
是否合理?答案是肯定的。由于引用折叠,
get<0>
tuple<int&>&
的返回类型为
int&
。下表显示了一些示例:

tuple
get<0>
[
const
]
tuple<int&>&
int&
[
const
]
tuple<int&&>&
int&
[
const
]
tuple<int&>&&
int&
[
const
]
tuple<int&&>&&
int&&

请注意,应用于

const
的顶级
tuple
不会影响返回类型,因为引用本身已经类似于
const
(一个引用只能绑定到一个对象)。

P2214R2中给出的引用元组和值元组之间转换的一个激励性示例是views::zip

zip
使用
tuple
(或
pair
)来包装多个
ranges::range_value_t
(通常为
T
)和
ranges::range_reference_t
(通常为
T&
)底层视图。例如,两个 
zip
 中的 
std::vector<int>
tuple<int, int>
range_value_t
,其 
tuple<int&, int&>
range_reference_t
(请注意,在这种情况下,
tuple<int, int>&
 不适合,因为我们有 
int
 的两个视图)而不是
tuple<int, int>
的一种视图)。我们真的希望这两种类型能够对 
T
T&
 进行建模。因此,
P2214R2建议添加以下重载:

template<class... Us> tuple(tuple<Us...>&); // (1) template<class... Us> tuple(const tuple<Us...>&&); // (2)
添加重载 (2) 只是为了一致性和完整性。


但是等等,当我们可以利用完美转发时,为什么还要添加所有这些样板呢?事实上,

P2165R4建议添加以下通用构造函数:

template<tuple-like U> tuple(U&&);
那么我们可以摆脱所有那些冗余的单

tuple

/
pair
构造函数吗?出于向后兼容性的原因,可能不会。我们就保留它们吧,因为它们在所有情况下看起来都是“无害的”。不要惊讶 
tuple
 现在有 12 个构造函数(不包括复制和移动构造函数)。然而,确实存在这些冗余构造函数是不受欢迎的并且可能有害的情况。考虑以下代码(
Godbolt):

using T = std::tuple<int &>; using U = std::tuple<int &&>; T t = U(42);
上面的代码调用 

const tuple<int&&>&

 重载并立即创建一个悬空引用。在这种情况下,
tuple<int&&>&&
 重载不可行,因为 
int&&
 无法绑定到 
int&
。然后我们回到 
const tuple<int&&>&
,如上所示,它会产生一个 
int&
。如果没有这些重载,一个通用构造函数就可以正确拒绝上述代码。

© www.soinside.com 2019 - 2024. All rights reserved.