为什么原始卷曲构造函数 {} return 不是右值?
Why doesn't raw curly constructor {} return an rvalue?
假设您有一个带有 std::tuple
的可变参数 class,可以使用 args + 1 个新 arg 移动构造。当使用 std::apply()
和原始花括号构造函数构造时,该构造函数不会 return 右值。这意味着 class 不是移动构造的。下面举例说明。
#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 latest、clang latest 和 gcc latest 上测试。都有相同的结果。 (魔杖盒:https://wandbox.org/permlink/IQqqlLcmeyOzsJHC)
所以问题是,为什么 return 不是右值?我显然在 curly 构造函数中遗漏了一些东西。这可能与可变参数无关,但我想我不妨展示真实的场景。
Why doesn't raw curly constructor {} return an rvalue?
问题是另一个。
问题在于
data = { std::move(ds)..., {} };
调用"direct constructor"(this page中的构造函数(2)),
constexpr tuple( const Types&... args ); (2)
不是 "converting constructor"(构造函数 (3))
template< class... UTypes >
constexpr tuple( UTypes&&... args ); (3)
如您所愿。
问题是“{}
”对于编译器来说还不足以推导出类型(构造函数 (3) 中 UTypes...
列表的最后一个类型),因此构造函数 ( 3) 被排除,编译器选择构造函数 (2)。
Whit constructor (2), "{}
" 可以构造列表的 Types...
最后类型的对象,因为 Types...
是已知的而不是被推导。
但是constructor(2)是copy constructor(从元组的Types...
来看),不像constructor(3)那样是forward constructor,所以第一个vector是copy的,不是感动了。
调用时不一样
data = { std::move(ds)..., std::move(std::vector<double>{}) };
或
data = { std::move(ds)..., std::vector<double>{} };
因为最后一个参数可以清楚地推断为std::vector<double>{} &&
所以编译器调用"converting constructor"(构造函数(3))并移动第一个向量的内容。
题外话:不要使用 std::vector<double>{}
,只有当 double
是 Args...
中的最后一个类型时才有效,我建议使用 [= 编写更通用的代码26=].
此外,我建议 SFINAE 仅在 sizeof...(MoreArgs)+1u == sizeof...(Args)
.
时启用您的构造函数
也许 std::forward()
(启用完美转发)而不是 lambda 中的 std::move()
。
所以我建议使用以下构造函数
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));
}
假设您有一个带有 std::tuple
的可变参数 class,可以使用 args + 1 个新 arg 移动构造。当使用 std::apply()
和原始花括号构造函数构造时,该构造函数不会 return 右值。这意味着 class 不是移动构造的。下面举例说明。
#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 latest、clang latest 和 gcc latest 上测试。都有相同的结果。 (魔杖盒:https://wandbox.org/permlink/IQqqlLcmeyOzsJHC)
所以问题是,为什么 return 不是右值?我显然在 curly 构造函数中遗漏了一些东西。这可能与可变参数无关,但我想我不妨展示真实的场景。
Why doesn't raw curly constructor {} return an rvalue?
问题是另一个。
问题在于
data = { std::move(ds)..., {} };
调用"direct constructor"(this page中的构造函数(2)),
constexpr tuple( const Types&... args ); (2)
不是 "converting constructor"(构造函数 (3))
template< class... UTypes > constexpr tuple( UTypes&&... args ); (3)
如您所愿。
问题是“{}
”对于编译器来说还不足以推导出类型(构造函数 (3) 中 UTypes...
列表的最后一个类型),因此构造函数 ( 3) 被排除,编译器选择构造函数 (2)。
Whit constructor (2), "{}
" 可以构造列表的 Types...
最后类型的对象,因为 Types...
是已知的而不是被推导。
但是constructor(2)是copy constructor(从元组的Types...
来看),不像constructor(3)那样是forward constructor,所以第一个vector是copy的,不是感动了。
调用时不一样
data = { std::move(ds)..., std::move(std::vector<double>{}) };
或
data = { std::move(ds)..., std::vector<double>{} };
因为最后一个参数可以清楚地推断为std::vector<double>{} &&
所以编译器调用"converting constructor"(构造函数(3))并移动第一个向量的内容。
题外话:不要使用 std::vector<double>{}
,只有当 double
是 Args...
中的最后一个类型时才有效,我建议使用 [= 编写更通用的代码26=].
此外,我建议 SFINAE 仅在 sizeof...(MoreArgs)+1u == sizeof...(Args)
.
也许 std::forward()
(启用完美转发)而不是 lambda 中的 std::move()
。
所以我建议使用以下构造函数
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));
}