Sam*_*son 7 c++ variadic-templates
我想在变量arity的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) {
???
}
Run Code Online (Sandbox Code Playgroud)
我有一个很长的答案,然后我改变了主意,使解决方案缩短了很多.但是我要展示我的思考过程并给你两个答案!
我的第一步是确定正确的签名.我不明白所有这些,但您可以将参数包视为以逗号分隔的实际项目列表,并隐藏文本转储.您可以通过更多以逗号分隔的项目来扩展列表!所以直接应用:
template <typename R, typename T, typename... Vargs>
std::vector<R> zipWith (R func(T,Vargs...), std::vector<T> first, Vargs rest) {
???
}
Run Code Online (Sandbox Code Playgroud)
您必须在表达式部分的参数包之后放置一个"..."以查看展开的列表.您还必须在常规参数部分中放置一个:
template <typename R, typename T, typename... Vargs>
std::vector<R> zipWith (R func(T,Vargs...), std::vector<T> first, Vargs... rest) {
???
}
Run Code Online (Sandbox Code Playgroud)
你说你的函数参数是一堆向量.在这里,你希望每一个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) {
???
}
Run Code Online (Sandbox Code Playgroud)
向量可以是巨大的对象,所以让我们使用constl值引用.另外,我们可以使用,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) {
???
}
Run Code Online (Sandbox Code Playgroud)
(我在这里遇到了std::pow用于测试的问题.我的编译器不会接受将经典函数指针转换为std::function对象.所以我必须将它包装在lambda中.也许我应该在这里问一下......)
此时,我重新加载页面并看到一个响应(按pmr).我真的不明白这种拉链,折叠,爆炸,无论什么东西,所以我认为他/她的解决方案太复杂了.所以我想到了一个更直接的解决方案:
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;
}
Run Code Online (Sandbox Code Playgroud)
我将创建一个元组向量,其中每个元组都是从每个向量中提取相应的元素.然后我将通过传递元组和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;
}
Run Code Online (Sandbox Code Playgroud)
第一行的第一部分允许函数访问自己的返回类型而无需复制和粘贴.唯一需要注意的是,r值引用参数必须被std::forward(或move)包围,因此不会错误地选择递归调用的l值重载.改变每个元组元素的一部分的函数必须显式地获取当前索引.在参数包剥离期间,索引向上移动一个:
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... );
}
Run Code Online (Sandbox Code Playgroud)
该表与最短的输入向量一样长,因此我们必须在当前输入向量首先结束时修整表.(我希望我能标记fe为const的范围内for,块)我本来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 )
{
//???
}
Run Code Online (Sandbox Code Playgroud)
我可以做个别案件:
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) ); }
Run Code Online (Sandbox Code Playgroud)
但我无法将其概括为递归案例.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;
}
Run Code Online (Sandbox Code Playgroud)
在那里,我被迫事先计算出呼叫总数:
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...) ); }
Run Code Online (Sandbox Code Playgroud)
果然,它奏效了!当然,这意味着我过度思考问题与其他受访者一样(以不同的方式).这也意味着我对这篇文章的大部分内容都不必要地厌倦了你.当我把它包起来时,我意识到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;
}
Run Code Online (Sandbox Code Playgroud)
我在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;
}
Run Code Online (Sandbox Code Playgroud)
使用r值引用标记函数参数是通用传递方法.它处理各种参考和const/ volatile(cv)资格.这就是我改用containers它的原因.在func可能有任何结构; 它甚至可以是具有多个版本的类对象operator ().由于我正在为容器使用r值,因此它们将使用最佳的cv资格来进行元素解除引用,并且该函数可以使用它来进行重载解析.内部确定结果类型的递归"调用"需要std::forward用来防止任何"降级"到l值引用.它还揭示了此迭代中的一个缺陷:我必须提供返回类型.
我会解决这个问题,但首先我要解释一下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;
}
Run Code Online (Sandbox Code Playgroud)
我复制了minimum_common_size这里,但明确提到了最小基础案例的结果类型,使用不同的大小类型校对不同的容器类型.
采用输出迭代器的函数通常在完成所有迭代器后返回迭代器.这使您可以在中断的位置开始新的输出运行(即使使用不同的输出功能).它对于标准输出迭代器并不重要,因为它们都是伪迭代器.使用前向迭代器(或以上)作为输出迭代器非常重要,因为它们会跟踪位置.(使用前向迭代器作为输出,只要最大传输次数不超过剩余的迭代空间,就是安全的.)有些函数将输出迭代器放在参数列表的末尾,其他函数放在开头; zip_width必须使用后者,因为参数包必须在最后.
zipWith在计算返回类型表达式时,移动到后缀返回类型会使函数的签名公平游戏的每个部分都成为可能.如果由于编译时不兼容而无法完成计算,它也会立即让我知道.该std::back_inserter函数返回一个特殊的输出迭代器,它通过push_back成员函数添加元素.