c++ - 为什么原始卷曲构造函数 {} 不返回右值?
问题描述
假设您有一个带有 a 的可变参数类std::tuple
,可以使用 args + 1 new arg 构造它。当使用std::apply()
原始花括号构造函数构造时,该构造函数不返回右值。这意味着类不是移动构造的。下面举例说明。
#include <cstdio>
#include <tuple>
#include <type_traits>
#include <unordered_map>
#include <vector>
template <class... Args>
struct icecream {
icecream() = default;
template <class... MoreArgs>
icecream(icecream<MoreArgs...>&& ice) {
std::apply(
[this](auto&&... ds) {
data = { std::move(ds)..., {} };
},
std::move(ice.data));
}
// This works :
// template <class... MoreArgs>
// icecream(icecream<MoreArgs...>&& ice) {
// std::apply(
// [this](auto&&... ds) {
// data = { std::move(ds)...,
// std::move(std::vector<double>{}) };
// },
// std::move(ice.data));
// }
std::tuple<std::vector<Args>...> data{};
};
int main(int, char**) {
icecream<int> miam;
std::get<0>(miam.data).push_back(1);
std::get<0>(miam.data).push_back(2);
icecream<int, double> cherry_garcia{ std::move(miam) };
printf("miam : \n");
for (const auto& x : std::get<0>(miam.data)) {
printf("%d\n", x);
}
printf("\ncherry_garcia : \n");
for (const auto& x : std::get<0>(cherry_garcia.data)) {
printf("%d\n", x);
}
return 0;
}
输出是:
miam :
1
2
cherry_garcia :
1
2
这个例子有点愚蠢,但说明了这一点。在第一个移动构造函数中,{}
使用了元组复制构造。如果您使用硬编码取消注释第二个构造函数std::move()
,则它可以工作。
我在最新的 VS、最新的 clang 和最新的 gcc 上进行测试。都有相同的结果。(魔杖盒: https ://wandbox.org/permlink/IQqqlLcmeyOzsJHC )
所以问题是,为什么不返回一个右值呢?我显然缺少 curly 构造函数的一些东西。这可能与可变参数无关,但我想我不妨展示一下真实场景。
解决方案
为什么原始卷曲构造函数 {} 不返回右值?
问题是另一个。
问题是
data = { std::move(ds)..., {} };
调用“直接构造函数”(本页中的构造函数(2) ),
constexpr tuple( const Types&... args ); (2)
不是“转换构造函数”(构造函数(3))
template< class... UTypes > constexpr tuple( UTypes&&... args ); (3)
你所期望的。
问题是{}
,对于编译器来说,“”不足以推断出一个类型(构造函数 (3) 中UTypes...
列表的最后一个类型),因此构造函数 (3) 被排除在外,编译器选择构造函数 (2)。
Whit 构造函数 (2),“ {}
” 可以构造Types...
列表的最后一个类型的对象,因为它Types...
是已知的而不是推导的。
但是构造函数(2)是一个复制构造函数(从元组的角度来看Types...
),而不是构造函数(3)的前向构造函数,所以第一个向量是复制的,而不是移动的。
打电话的时候不一样
data = { std::move(ds)..., std::move(std::vector<double>{}) };
或者也
data = { std::move(ds)..., std::vector<double>{} };
因为可以清楚地推导出最后一个参数,std::vector<double>{} &&
所以编译器调用“转换构造函数”(构造函数(3))并移动第一个向量的内容。
题外话:我建议std::vector<double>{}
使用double
.Args...
std::tuple_element
此外,我建议 SFINAE 仅在sizeof...(MoreArgs)+1u == sizeof...(Args)
.
也许也std::forward()
(启用完美转发)而不是std::move()
在 lambda 中。
所以我建议以下构造函数
template <typename ... MoreArgs,
std::enable_if_t<sizeof...(MoreArgs)+1u == sizeof...(Args)> * = nullptr>
icecream(icecream<MoreArgs...>&& ice) {
std::apply(
[this](auto && ... ds) {
data = { std::forward<decltype(ds)>(ds)...,
std::tuple_element_t<sizeof...(Args)-1u,
decltype(data)>{} };
},
std::move(ice.data));
}
推荐阅读
- c# - 在 docker 容器外公开日志文件
- javascript - React.JS 组件类中的函数未定义错误
- google-chrome - HTML5 视频立即停止(在 Firefox、Opera 中运行良好)
- node.js - Puppeteer,如何关闭浏览器并保存会话?在登录页面上进行用户交互后?
- automation - 在 AppleScripts 旁边使用条形码扫描仪
- typescript - 将标记映射到标记的联合成员不适用于特定的联合,仅适用于通用的联合
- reactjs - React-Navigation with Onboarding 效果不佳
- javascript - 返回值和将值传递给函数有什么区别?
- h2o4gpu - 通过 H2O.ai v3 Web Ui 使用 H2O4GPU
- r - 如何在依赖列上使用收集