将 n 个向量组合成一个 n 元组向量
Combining n vectors into one vector of n-tuples
我正在考虑带有签名的函数
template<typename ...Ts>
std::vector<std::tuple<Ts...>> join_vectors(std::vector<Ts>&&...) {
//...
};
但可能更通用的接受任何可迭代对象而不仅仅是 std::vector
会更好。大概会有这样的签名吧?
template<template<typename> typename C, typename ...Ts>
C<std::tuple<Ts...>> join_vectors(C<Ts>&&...) {
// ...
};
但是,我在 C++ 方面还没有达到这个水平(尽管在 Haskell 中做同样的事情会相对容易),因此我寻求帮助。
不幸的是,在这种情况下,Range-v3 的 zip
不在我的支配范围内。我标记它是因为我认为对它感兴趣的人可以更好地帮助我。
对于具有 size
的任何可索引容器,这样的事情是可能的:
#include <tuple>
#include <vector>
#include <algorithm>
// Copy from lvalue containers, move from rvalue containers.
template<typename ...Cs>
auto zip(Cs... vecs) {
std::vector<std::tuple<typename std::decay_t<Cs>::value_type...>> vec;
auto len = std::min({vecs.size()...});
vec.reserve(len);
for(std::size_t i=0;i<len;++i){
vec.emplace_back(std::move(vecs[i])...);
}
return vec;
};
//Return vector of tuples with & for non-const vecs and const& if const.
template<typename ...Cs>
auto zip_view(Cs&... vecs) {
std::vector<std::tuple<decltype(vecs[0])...>> vec;
auto len = std::min({vecs.size()...});
vec.reserve(len);
for(std::size_t i=0;i<len;++i){
vec.emplace_back(vecs[i]...);
}
return vec;
};
如果容器正确实现了移动构造函数,此解决方案将复制作为左值传递的容器并从右值传递的容器移动。
非常轻微的缺点是左值容器首先被整个复制,而不是仅复制单个元素。
示例[Godbolt]
#include <iostream>
#include <memory>
template<typename T, typename...Args>
void print_tuple(const T& first, const Args&... args){
std::cout<<'('<<first;
((std::cout<<','<< args),...);
std::cout<<')';
}
template<typename T>
struct helper{
using fnc_t = void;
};
template<typename...Args>
struct helper<std::tuple<Args...>>{
using fnc_t = void(*)(const Args&... args);
};
template<typename...Args>
struct helper<std::tuple<Args&...>>{
using fnc_t = void(*)(const Args&... args);
};
template<typename T>
using fnc_t2 = typename helper<T>::fnc_t;
template<typename T>
void template_apply(fnc_t2<T> f, const T& tuple){
std::apply(f, tuple);
}
template<typename T>
void print_vec(const std::vector<T>& vec){
for(const auto&e:vec){
template_apply(print_tuple,e);
std::cout<<'\n';
}
}
struct MoveOnlyFoo{
MoveOnlyFoo(int i):m_i(i){}
int m_i;
std::unique_ptr<int> ptr = nullptr;
};
std::ostream& operator<<(std::ostream& o, const MoveOnlyFoo& foo){
return o<<foo.m_i;
}
int main(){
std::vector v1{1,2,3,4,5,6};
std::vector v2{'a','b','c','d','e'};
std::vector v3{1.5,3.5,7.5};
std::vector<MoveOnlyFoo> vmove;
vmove.emplace_back(45);
vmove.emplace_back(46);
vmove.emplace_back(47);
const std::vector v4{-1,-2,-3,-4,-5};
//Move rvalues, copy lvalue.
print_vec(zip(v1,v2,v3, v4, std::move(vmove)));
// This won't work since the elements from the last vector cannot be copied.
//print_vec(zip(v1,v2,v3, v4, vmove));
std::cout<<"View:\n";
//View, provides const& for const inputs, & for non-const
print_vec(zip_view(v1,v2,v3,v4));
std::cout<<"Modify and print:\n";
for(auto& [x,y]: zip_view(v1,v2)){
++x,++y;
}
// Note the view can work with const containers, returns tuple of `const T&`.
print_vec(zip_view(std::as_const(v1),std::as_const(v2)));
}
输出
(1,a,1.5,-1,45)
(2,b,3.5,-2,46)
(3,c,7.5,-3,47)
View:
(1,a,1.5,-1)
(2,b,3.5,-2)
(3,c,7.5,-3)
Modify and print:
(2,b)
(3,c)
(4,d)
(5,e)
(6,f)
请忽略打印代码的可读性;)
我根据 python zip
功能对其进行了建模。请注意,您的初始提案复制了向量,因此输出是一个向量,其中的值从参数中移出。
返回可迭代对象 Cs
更难,因为您必须指定如何将元素插入其中,迭代器无法自行完成。
让它与迭代器一起工作(但仍然返回一个向量)是一件苦差事,但理论上也是可行的。
我正在考虑带有签名的函数
template<typename ...Ts>
std::vector<std::tuple<Ts...>> join_vectors(std::vector<Ts>&&...) {
//...
};
但可能更通用的接受任何可迭代对象而不仅仅是 std::vector
会更好。大概会有这样的签名吧?
template<template<typename> typename C, typename ...Ts>
C<std::tuple<Ts...>> join_vectors(C<Ts>&&...) {
// ...
};
但是,我在 C++ 方面还没有达到这个水平(尽管在 Haskell 中做同样的事情会相对容易),因此我寻求帮助。
不幸的是,在这种情况下,Range-v3 的 zip
不在我的支配范围内。我标记它是因为我认为对它感兴趣的人可以更好地帮助我。
对于具有 size
的任何可索引容器,这样的事情是可能的:
#include <tuple>
#include <vector>
#include <algorithm>
// Copy from lvalue containers, move from rvalue containers.
template<typename ...Cs>
auto zip(Cs... vecs) {
std::vector<std::tuple<typename std::decay_t<Cs>::value_type...>> vec;
auto len = std::min({vecs.size()...});
vec.reserve(len);
for(std::size_t i=0;i<len;++i){
vec.emplace_back(std::move(vecs[i])...);
}
return vec;
};
//Return vector of tuples with & for non-const vecs and const& if const.
template<typename ...Cs>
auto zip_view(Cs&... vecs) {
std::vector<std::tuple<decltype(vecs[0])...>> vec;
auto len = std::min({vecs.size()...});
vec.reserve(len);
for(std::size_t i=0;i<len;++i){
vec.emplace_back(vecs[i]...);
}
return vec;
};
如果容器正确实现了移动构造函数,此解决方案将复制作为左值传递的容器并从右值传递的容器移动。 非常轻微的缺点是左值容器首先被整个复制,而不是仅复制单个元素。
示例[Godbolt]
#include <iostream>
#include <memory>
template<typename T, typename...Args>
void print_tuple(const T& first, const Args&... args){
std::cout<<'('<<first;
((std::cout<<','<< args),...);
std::cout<<')';
}
template<typename T>
struct helper{
using fnc_t = void;
};
template<typename...Args>
struct helper<std::tuple<Args...>>{
using fnc_t = void(*)(const Args&... args);
};
template<typename...Args>
struct helper<std::tuple<Args&...>>{
using fnc_t = void(*)(const Args&... args);
};
template<typename T>
using fnc_t2 = typename helper<T>::fnc_t;
template<typename T>
void template_apply(fnc_t2<T> f, const T& tuple){
std::apply(f, tuple);
}
template<typename T>
void print_vec(const std::vector<T>& vec){
for(const auto&e:vec){
template_apply(print_tuple,e);
std::cout<<'\n';
}
}
struct MoveOnlyFoo{
MoveOnlyFoo(int i):m_i(i){}
int m_i;
std::unique_ptr<int> ptr = nullptr;
};
std::ostream& operator<<(std::ostream& o, const MoveOnlyFoo& foo){
return o<<foo.m_i;
}
int main(){
std::vector v1{1,2,3,4,5,6};
std::vector v2{'a','b','c','d','e'};
std::vector v3{1.5,3.5,7.5};
std::vector<MoveOnlyFoo> vmove;
vmove.emplace_back(45);
vmove.emplace_back(46);
vmove.emplace_back(47);
const std::vector v4{-1,-2,-3,-4,-5};
//Move rvalues, copy lvalue.
print_vec(zip(v1,v2,v3, v4, std::move(vmove)));
// This won't work since the elements from the last vector cannot be copied.
//print_vec(zip(v1,v2,v3, v4, vmove));
std::cout<<"View:\n";
//View, provides const& for const inputs, & for non-const
print_vec(zip_view(v1,v2,v3,v4));
std::cout<<"Modify and print:\n";
for(auto& [x,y]: zip_view(v1,v2)){
++x,++y;
}
// Note the view can work with const containers, returns tuple of `const T&`.
print_vec(zip_view(std::as_const(v1),std::as_const(v2)));
}
输出
(1,a,1.5,-1,45)
(2,b,3.5,-2,46)
(3,c,7.5,-3,47)
View:
(1,a,1.5,-1)
(2,b,3.5,-2)
(3,c,7.5,-3)
Modify and print:
(2,b)
(3,c)
(4,d)
(5,e)
(6,f)
请忽略打印代码的可读性;)
我根据 python zip
功能对其进行了建模。请注意,您的初始提案复制了向量,因此输出是一个向量,其中的值从参数中移出。
返回可迭代对象 Cs
更难,因为您必须指定如何将元素插入其中,迭代器无法自行完成。
让它与迭代器一起工作(但仍然返回一个向量)是一件苦差事,但理论上也是可行的。