我想在变量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) {
???
}
答案 0 :(得分:7)
我得到了一个很长的答案,然后我以一种使解决方案缩短的方式改变了主意。但我会展示我的思考过程并给你两个答案!
我的第一步是确定正确的签名。我并不了解所有内容,但您可以将参数包视为以逗号分隔的实际项目列表,并隐藏文本转储。您可以通过更多以逗号分隔的项目来扩展列表!所以直接应用:
template <typename R, typename T, typename... Vargs>
std::vector<R> zipWith (R func(T,Vargs...), std::vector<T> first, Vargs rest) {
???
}
你必须放一个&#34; ...&#34;在表达式部分的参数包之后查看扩展列表。您还必须在常规参数部分中添加一个:
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
l值引用。另外,我们可以使用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
对象。所以我不得不把它包装成一个lambda。也许我应该在这里问一下......)
此时,我重新加载页面并看到一个response (by 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;
}
我会创建一个元组向量,其中每个元组都是从每个向量中提取相应的元素。然后我会创建一个矢量
评估结果来自每次传递一个元组和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值重载不会被错误地选中。改变每个元组元素的一部分的函数必须显式地获取当前索引。在参数包剥离期间,索引向上移动一次:
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... );
}
该表与最短的输入向量一样长,因此我们必须在当前输入向量首先结束时修整表。 (我希望我可以在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 )
{
//???
}
我可以做个别案件:
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资格来进行元素解除引用,并且该函数可以使用它来进行重载解析。递归&#34;电话&#34;在内部确定结果类型需要使用std::forward
来防止任何&#34;降级&#34; 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;
}
我在这里复制了minimum_common_size
,但明确提到了最小基本案例的结果类型,使用不同的大小类型对不同的容器类型进行校对。
采用输出迭代器的函数通常在所有迭代器完成后返回迭代器。这使您可以在中断的位置开始新的输出运行(即使使用不同的输出功能)。它对标准输出迭代器并不重要,因为它们都是伪迭代器。使用前向迭代器(或以上)作为输出迭代器非常重要,因为它们执行跟踪位置。 (使用前向迭代器作为输出,只要最大传输次数不超过剩余的迭代空间,就是安全的。)有些函数将输出迭代器放在参数列表的末尾,其他函数放在开头; zip_width
必须使用后者,因为参数包必须在最后。
在计算返回类型表达式时,移动到zipWith
中的后缀返回类型会使函数的签名公平游戏的每个部分都生成。如果由于编译时的不兼容性而无法完成计算,它也会立即让我知道。 std::back_inserter
函数向向量返回一个特殊的输出迭代器,它通过push_back
成员函数添加元素。
答案 1 :(得分:3)
这是我拼凑的东西:
#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;
}
与以前的版本相比,这是一个稍微改进的版本。像每一个 好的程序应该,它首先定义一个左折。
它仍然无法解决成对打包的迭代器问题。
在stdlib术语中,此函数将被称为transform
并且会
要求只指定一个序列的长度和
其他人至少要这么久。我在这里称它为transformV
以避免
名字冲突。