有可能在c++中编写一个通用的可变zipWith吗?< / h1 >
Is it possible to write a generic variadic zipWith in C++?
我想要一个通用的变量的c++ zipWith函数。我有两个问题。首先,我无法确定传递给zipWith的函数指针的类型。它必须与传递给zipWith的向量数量具有相同的密度,并且必须分别接受对向量元素类型的引用。第二,我不知道如何并行遍历这些向量来构建一个参数列表,调用func(),一旦最短的向量耗尽就退出。
template <typename R, typename T, typename... Vargs>
std::vector<R> zipWith (R func(???<what goes here>), std::vector<T> first, Vargs rest) {
???
}
我有一个很长的答案,然后我改变了主意,使解决方案更短。但我将展示我的思考过程,并给出两个答案!
我的第一步是确定正确的签名。我不完全理解它,但是您可以将参数包视为隐藏文本转储的以逗号分隔的实际项列表。您可以在任意一侧通过更多逗号分隔的项目扩展列表!直接应用
template <typename R, typename T, typename... Vargs>
std::vector<R> zipWith (R func(T,Vargs...), std::vector<T> first, Vargs rest) {
???
}
你必须在表达式部分的参数包后面放一个"…"来查看展开的列表。您必须在常规参数部分也放一个:
template <typename R, typename T, typename... Vargs>
std::vector<R> zipWith (R func(T,Vargs...), std::vector<T> first, Vargs... rest) {
???
}
你说你的函数参数是一堆向量。这里,你希望每个Vargs
都是一个std::vector
。类型转换可以应用于参数包,所以为什么我们不确保你有向量:
template <typename R, typename T, typename... Vargs>
std::vector<R> zipWith (R func(T,Vargs...), std::vector<T> first, std::vector<Vargs> ...rest) {
???
}
向量可以是巨大的对象,所以让我们使用const
左值引用。此外,我们可以使用std::function
,因此我们可以使用lambda或std::bind
表达式:
template <typename R, typename T, typename... Vargs>
std::vector<R> zipWith (std::function<R(T, Vargs...)> func, std::vector<T> const &first, std::vector<Vargs> const &...rest) {
???
}
(我在这里遇到了使用std::pow
进行测试的问题。我的编译器不接受将经典函数指针转换为std::function
对象。所以我要把它包起来。也许我应该在这里问一下....)
template < typename R, typename T, typename ...MoreTs >
std::vector<R>
zip_with( std::function<R(T,MoreTs...)> func,
const std::vector<T>& first, const std::vector<MoreTs>& ...rest )
{
auto const tuples = rearrange_vectors( first, rest... );
std::vector<R> result;
result.reserve( tuples.size() );
for ( auto const &x : tuples )
result.push_back( evaluate(x, func) );
return result;
}
我将创建一个元组的向量,其中每个元组是通过从每个向量中提取相应的元素而形成的。然后我就得到一个向量
每次传递一个元组和func
。rearrange_vectors
必须提前制作值表(默认构造),并一次填写每个条目的子对象:
template < typename T, typename ...MoreTs >
std::vector<std::tuple<T, MoreTs...>>
rearrange_vectors( const std::vector<T>& first,
const std::vector<MoreTs>& ...rest )
{
decltype(rearrange_vectors(first, rest...))
result( first.size() );
fill_vector_perpendicularly<0>( result, first, rest... );
return result;
}
第一行的第一部分允许函数访问自己的返回类型,而不需要复制粘贴。唯一需要注意的是,r值引用参数必须被std::forward
(或move
)包围,这样递归调用的l值重载就不会被错误地选择。改变每个元组元素的部分的函数必须显式地采用当前索引。参数包剥离时,索引向上移动1:
template < std::size_t, typename ...U >
void fill_vector_perpendicularly( std::vector<std::tuple<U...>>& )
{ }
template < std::size_t I, class Seq, class ...MoreSeqs, typename ...U >
void fill_vector_perpendicularly( std::vector<std::tuple<U...>>&
table, const Seq& first, const MoreSeqs& ...rest )
{
auto t = table.begin();
auto const te = table.end();
for ( auto f = first.begin(), fe = first.end(); (te != t) && (fe
!= f) ; ++t, ++f )
std::get<I>( *t ) = *f;
table.erase( t, te );
fill_vector_perpendicularly<I + 1u>( table, rest... );
}
表和最短的输入向量一样长,所以我们必须在当前输入向量首先结束时修剪表。(我希望我可以在for
块中将fe
标记为const
。)我最初将first
和rest
作为std::vector
,但我意识到我可以将其抽象出来;我所需要的是与迭代接口中的标准(序列)容器匹配的类型。但现在我被evaluate
难住了:
template < typename R, typename T, typename ...MoreTs >
R evaluate( const std::tuple<T, MoreTs...>& x,
std::function<R(T,MoreTs...)> func )
{
//???
}
我可以做个别情况:
template < typename R >
R evaluate( const std::tuple<>& x, std::function<R()> func )
{ return func(); }
template < typename R, typename T >
R evaluate( const std::tuple<T>& x, std::function<R(T)> func )
{ return func( std::get<0>(x) ); }
但是我不能推广到递归的情况。IIUC,std::tuple
不支持剥离子元组的尾部(和/或头部)。std::bind
也不支持分段地将参数套用到函数中,而且它的占位符系统与任意长度的参数包不兼容。我希望我能列出每个参数就像我可以访问原始输入向量....
…等等,为什么不我就这样做? !…
…我从来没听说过。我见过将模板参数包转移到函数参数;我刚在zipWith
展示过。我可以从函数参数列表到函数内部吗?(在我写这篇文章的时候,我现在记得在类构造函数的成员初始化部分看到过它,用于数组或类类型的非静态成员。)只有一个办法可以知道:
template < typename R, typename T, typename ...MoreTs >
std::vector<R>
zip_with( std::function<R(T,MoreTs...)> func, const std::vector<T>&
first, const std::vector<MoreTs>& ...rest )
{
auto const s = minimum_common_size( first, rest... );
decltype(zip_with(func,first,rest...)) result;
result.reserve( s );
for ( std::size_t i = 0 ; i < s ; ++i )
result.push_back( func(first[i], rest[i]...) );
return result;
}
,我必须事先计算调用的总数:
inline std::size_t minimum_common_size() { return 0u; }
template < class SizedSequence >
std::size_t minimum_common_size( const SizedSequence& first )
{ return first.size(); }
template < class Seq, class ...MoreSeqs >
std::size_t
minimum_common_size( const Seq& first, const MoreSeqs& ...rest )
{ return std::min( first.size(), minimum_common_size(rest...) ); }
,果然起作用了!当然,这意味着我和其他受访者一样,对这个问题想得太多了(只是方式不同)。这也意味着我没有必要用这篇文章的大部分内容来让你感到无聊。当我总结这一点时,我意识到用泛型序列容器类型替换std::vector
可以应用于zip_width
。我意识到我可以把强制性的一个向量减少到没有强制性的向量:
template < typename R, typename ...T, class ...SizedSequences >
std::vector<R>
zip_with( R func(T...) /*std::function<R(T...)> func*/,
SizedSequences const& ...containers )
{
static_assert( sizeof...(T) == sizeof...(SizedSequences),
"The input and processing lengths don't match." );
auto const s = minimum_common_size( containers... );
decltype( zip_with(func, containers...) ) result;
result.reserve( s );
for ( std::size_t i = 0 ; i < s ; ++i )
result.push_back( func(containers[i]...) );
return result;
}
我在复制这里的代码时添加了static_assert
,因为我忘记确保func
的参数计数和输入向量的数量一致。现在我意识到我可以通过抽象两者来修复函数指针与std::function
对象之间的决斗:
template < typename R, typename Func, class ...SizedSequences >
std::vector<R>
zip_with( Func&& func, SizedSequences&& ...containers )
{
auto const s = minimum_common_size( containers... );
decltype( zip_with<R>(std::forward<Func>(func),
std::forward<SizedSequences>(containers)...) ) result;
result.reserve( s );
for ( std::size_t i = 0 ; i < s ; ++i )
result.push_back( func(containers[i]...) );
return result;
}
用r值引用标记函数参数是通用传递方法。它处理各种推荐信和const
/volatile
(cv)资格。这就是为什么我把containers
换成它。func
可以有任何结构;它甚至可以是具有多个版本operator ()
的类对象。由于我对容器使用r值,因此它们将使用最佳的cv-限定符来解引用元素,并且函数可以使用它来进行重载解析。内部确定结果类型的递归"调用"需要使用std::forward
来防止任何"降级"到左值引用。它还揭示了这个迭代中的一个缺陷:I必须提供返回类型
我会解决这个问题,但首先我想解释一下STL的方式。您不需要预先确定特定的容器类型并将其返回给用户。您请求一个特殊对象,一个输出迭代器,您将结果发送给它。迭代器可以连接到容器,标准提供了几种类型的容器。它可以连接到输出流,直接打印结果!迭代器方法还使我不必直接担心内存问题。
#include <algorithm>
#include <cstddef>
#include <iterator>
#include <utility>
#include <vector>
inline std::size_t minimum_common_size() { return 0u; }
template < class SizedSequence >
std::size_t minimum_common_size( const SizedSequence& first )
{ return first.size(); }
template < class Seq, class ...MoreSeqs >
std::size_t minimum_common_size( const Seq& first,
const MoreSeqs& ...rest )
{
return std::min<std::size_t>( first.size(),
minimum_common_size(rest...) );
}
template < typename OutIter, typename Func, class ...SizedSequences >
OutIter
zip_with( OutIter o, Func&& func, SizedSequences&& ...containers )
{
auto const s = minimum_common_size( containers... );
for ( std::size_t i = 0 ; i < s ; ++i )
*o++ = func( containers[i]... );
return o;
}
template < typename Func, class ...SizedSequences >
auto zipWith( Func&& func, SizedSequences&& ...containers )
-> std::vector<decltype( func(containers.front()...) )>
{
using std::forward;
decltype( zipWith(forward<Func>( func ), forward<SizedSequences>(
containers )...) ) result;
#if 1
// `std::vector` is the only standard container with the `reserve`
// member function. Using it saves time when doing multiple small
// inserts, since you'll do reallocation at most (hopefully) once.
// The cost is that `s` is already computed within `zip_with`, but
// we can't get at it. (Remember that most container types
// wouldn't need it.) Change the preprocessor flag to change the
// trade-off.
result.reserve( minimum_common_size(containers...) );
#endif
zip_with( std::back_inserter(result), forward<Func>(func),
forward<SizedSequences>(containers)... );
return result;
}
我在这里复制了minimum_common_size
,但明确提到了最小基数情况下的结果类型,以防止使用不同大小类型的不同容器类型。
接受输出迭代器的函数通常在所有迭代器完成后返回迭代器。这允许您在停止的地方开始新的输出运行(即使使用不同的输出函数)。这对于标准输出迭代器来说并不重要,因为它们都是伪迭代器。当使用前向迭代器(或以上)作为输出迭代器时,这一点很重要,因为它们执行跟踪位置。(只要最大传输次数不超过剩余的迭代空间,使用前向迭代器作为输出迭代器是安全的。)有些函数把输出迭代器放在参数列表的末尾,有些放在开头;zip_width
必须使用后者,因为参数包必须放在最后。
在zipWith
中移动到后缀返回类型使得函数签名的每个部分在计算返回类型表达式时都是公平的。它还可以让我立即知道,如果由于编译时的不兼容性而无法进行计算。std::back_inserter
函数向vector返回一个特殊的输出迭代器,该vector通过push_back
成员函数添加元素。
以下是我拼凑的内容:
#include <iostream>
#include <vector>
#include <utility>
template<typename F, typename T, typename Arg>
auto fold(F f, T&& t, Arg&& a)
-> decltype(f(std::forward<T>(t), std::forward<Arg>(a)))
{ return f(std::forward<T>(t), std::forward<Arg>(a)); }
template<typename F, typename T, typename Head, typename... Args>
auto fold(F f, T&& init, Head&& h, Args&&... args)
-> decltype(f(std::forward<T>(init), std::forward<Head>(h)))
{
return fold(f, f(std::forward<T>(init), std::forward<Head>(h)),
std::forward<Args>(args)...);
}
// hack in a fold for void functions
struct ignore {};
// cannot be a lambda, needs to be polymorphic on the iterator type
struct end_or {
template<typename InputIterator>
bool operator()(bool in, const std::pair<InputIterator, InputIterator>& p)
{ return in || p.first == p.second; }
};
// same same but different
struct inc {
template<typename InputIterator>
ignore operator()(ignore, std::pair<InputIterator, InputIterator>& p)
{ p.first++; return ignore(); }
};
template<typename Fun, typename OutputIterator,
typename... InputIterators>
void zipWith(Fun f, OutputIterator out,
std::pair<InputIterators, InputIterators>... inputs) {
if(fold(end_or(), false, inputs...)) return;
while(!fold(end_or(), false, inputs...)) {
*out++ = f( *(inputs.first)... );
fold(inc(), ignore(), inputs...);
}
}
template<typename Fun, typename OutputIterator,
typename InputIterator, typename... Rest>
void transformV(Fun f, OutputIterator out, InputIterator begin, InputIterator end,
Rest... rest)
{
if(begin == end) return ;
while(begin != end) {
*out++ = f(*begin, *(rest)... );
fold(inc2(), ignore(), begin, rest...);
}
}
struct ternary_plus {
template<typename T, typename U, typename V>
auto operator()(const T& t, const U& u, const V& v)
-> decltype( t + u + v) // common type?
{ return t + u + v; }
};
int main()
{
using namespace std;
vector<int> a = {1, 2, 3}, b = {1, 2}, c = {1, 2, 3};
vector<int> out;
zipWith(ternary_plus(), back_inserter(out)
, make_pair(begin(a), end(a))
, make_pair(begin(b), end(b))
, make_pair(begin(c), end(c)));
transformV(ternary_plus(), back_inserter(out),
begin(a), end(a), begin(b), begin(c));
for(auto x : out) {
std::cout << x << std::endl;
}
return 0;
}
这是一个比以前版本稍微改进的变体。因为每个好的程序应该从定义左折开始。
它仍然不能解决迭代器成对打包的问题。
在标准库术语中,该函数将被称为transform
,并将要求只指定一个序列的长度,并且其他的至少要这么长。我在这里叫它transformV
是为了避免名称冲突。
- 请解释这句话(cout<<1+int((a<b)^((b-a)&1) )<<endl
- 呼叫运营商<<临时
- 如何防止clang格式在流运算符调用之间添加换行符<<
- <<操作员在下面的行中工作
- 如何显式调用运算符<<
- 模板操作员&lt;未打电话
- C / CUDA中的模板方法是3个角括号(&lt;&lt;&lt;)
- C - 创建矢量&lt; vector&lt; double&gt;&gt;矩阵具有分配而不是inizializ
- 错误:调用"std::vector<:vector<int>>::p ush_back(std::vector<std::__cxx11::basic_string<
- C 建造者Clang STD :: Sill,找不到超载的操作员&lt;
- 为什么STD :: MAP需要操作员&lt;以及我如何写一个
- 为什么“操作员”需要const但不是为“运营商&lt;”
- 为什么将此对向量&lt; map&lt; int,int&gt;&gt;中的地图进行更新.失败
- C :对矢量进行排序&lt; struct&gt;(结构有2个整数)基于结构的整数之一
- 明确的专业化“ CheckIntmap&lt;&gt;”实例化
- 什么是模板&lt;&gt;inline bla bla
- 左角支架解释为操作员&lt;而不是模板参数
- 编辑C Qlist&lt; object*&gt; gt;QML代码和一些QML警告中的模型
- 超载操作员&lt;&lt; - 必须是二进制操作员
- 没有匹配的“运营商&lt;&lt;”