元组统一初始化
Uniform initialization by tuple
今天,我遇到了一种情况,我有一个元组向量,其中元组可能包含多个条目。现在我想将我的元组向量转换为对象向量,这样元组的条目将完全匹配我的对象的统一初始化。
下面的代码可以帮我完成这项工作,但它有点笨拙。我在问自己,如果元组与对象的统一初始化顺序完全匹配,是否有可能推导出一个可以构造对象的通用解决方案。
当要传递的参数数量增加时,这可能是一个非常理想的功能。
#include <vector>
#include <tuple>
#include <string>
#include <algorithm>
struct Object
{
std::string s;
int i;
double d;
};
int main() {
std::vector<std::tuple<std::string, int, double>> values = { {"A",0,0.},{"B",1,1.} };
std::vector<Object> objs;
std::transform(values.begin(), values.end(), std::back_inserter(objs), [](auto v)->Object
{
// This might get tedious to type, if the tuple grows
return { std::get<0>(v), std::get<1>(v), std::get<2>(v) };
// This is my desired behavior, but I don't know what magic_wrapper might be
// return magic_wrapper(v);
});
return EXIT_SUCCESS;
}
提供Object
一个std::tuple
构造函数。您可以使用 std::tie
分配您的成员:
template<typename ...Args>
Object(std::tuple<Args...> t) {
std::tie(s, i, d) = t;
}
现在自动构建:
std::transform(values.begin(), values.end(), std::back_inserter(objs),
[](auto v) -> Object {
return { v };
});
为了减少复制量,您可能需要将 auto v
替换为 const auto& v
并使构造函数接受 const std::tuple<Args...>& t
。
此外,通过 const
迭代器访问源容器是一种很好的做法:
std::transform(
values.cbegin(), values.cend()
, std::back_inserter(objs), ...
这是一个 non-intrusive 版本(即不涉及 Object
),它提取了指定数据成员的数量。请注意,这依赖于聚合初始化。
template <class T, class Src, std::size_t... Is>
constexpr auto createAggregateImpl(const Src& src, std::index_sequence<Is...>) {
return T{std::get<Is>(src)...};
}
template <class T, std::size_t n, class Src>
constexpr auto createAggregate(const Src& src) {
return createAggregateImpl<T>(src, std::make_index_sequence<n>{});
}
你这样调用它:
std::transform(values.cbegin(), values.cend(), std::back_inserter(objs),
[](const auto& v)->Object { return createAggregate<Object, 3>(v); });
或者,没有包装 lambda:
std::transform(values.cbegin(), values.cend(), std::back_inserter(objs),
createAggregate<Object, 3, decltype(values)::value_type>);
正如@Deduplicator 所指出的,上面的帮助模板实现了 std::apply
的部分,可以代替使用。
template <class T>
auto aggregateInit()
{
return [](auto&&... args) { return Object{std::forward<decltype(args)>(args)...}; };
}
std::transform(values.cbegin(), values.cend(), std::back_inserter(objs),
[](const auto& v)->Object { return std::apply(aggregateInit<Object>(), v); });
自 C++17 起,您可以使用 std::make_from_tuple:
std::transform(values.begin(),
values.end(),
std::back_inserter(objs),
[](const auto& t)
{
return std::make_from_tuple<Object>(t);
});
注意:Object
需要适当的构造函数。
今天,我遇到了一种情况,我有一个元组向量,其中元组可能包含多个条目。现在我想将我的元组向量转换为对象向量,这样元组的条目将完全匹配我的对象的统一初始化。
下面的代码可以帮我完成这项工作,但它有点笨拙。我在问自己,如果元组与对象的统一初始化顺序完全匹配,是否有可能推导出一个可以构造对象的通用解决方案。
当要传递的参数数量增加时,这可能是一个非常理想的功能。
#include <vector>
#include <tuple>
#include <string>
#include <algorithm>
struct Object
{
std::string s;
int i;
double d;
};
int main() {
std::vector<std::tuple<std::string, int, double>> values = { {"A",0,0.},{"B",1,1.} };
std::vector<Object> objs;
std::transform(values.begin(), values.end(), std::back_inserter(objs), [](auto v)->Object
{
// This might get tedious to type, if the tuple grows
return { std::get<0>(v), std::get<1>(v), std::get<2>(v) };
// This is my desired behavior, but I don't know what magic_wrapper might be
// return magic_wrapper(v);
});
return EXIT_SUCCESS;
}
提供Object
一个std::tuple
构造函数。您可以使用 std::tie
分配您的成员:
template<typename ...Args>
Object(std::tuple<Args...> t) {
std::tie(s, i, d) = t;
}
现在自动构建:
std::transform(values.begin(), values.end(), std::back_inserter(objs),
[](auto v) -> Object {
return { v };
});
为了减少复制量,您可能需要将 auto v
替换为 const auto& v
并使构造函数接受 const std::tuple<Args...>& t
。
此外,通过 const
迭代器访问源容器是一种很好的做法:
std::transform(
values.cbegin(), values.cend()
, std::back_inserter(objs), ...
这是一个 non-intrusive 版本(即不涉及 Object
),它提取了指定数据成员的数量。请注意,这依赖于聚合初始化。
template <class T, class Src, std::size_t... Is>
constexpr auto createAggregateImpl(const Src& src, std::index_sequence<Is...>) {
return T{std::get<Is>(src)...};
}
template <class T, std::size_t n, class Src>
constexpr auto createAggregate(const Src& src) {
return createAggregateImpl<T>(src, std::make_index_sequence<n>{});
}
你这样调用它:
std::transform(values.cbegin(), values.cend(), std::back_inserter(objs),
[](const auto& v)->Object { return createAggregate<Object, 3>(v); });
或者,没有包装 lambda:
std::transform(values.cbegin(), values.cend(), std::back_inserter(objs),
createAggregate<Object, 3, decltype(values)::value_type>);
正如@Deduplicator 所指出的,上面的帮助模板实现了 std::apply
的部分,可以代替使用。
template <class T>
auto aggregateInit()
{
return [](auto&&... args) { return Object{std::forward<decltype(args)>(args)...}; };
}
std::transform(values.cbegin(), values.cend(), std::back_inserter(objs),
[](const auto& v)->Object { return std::apply(aggregateInit<Object>(), v); });
自 C++17 起,您可以使用 std::make_from_tuple:
std::transform(values.begin(),
values.end(),
std::back_inserter(objs),
[](const auto& t)
{
return std::make_from_tuple<Object>(t);
});
注意:Object
需要适当的构造函数。