Is it possible to write a generic variadic zipWith in C++?

后端 未结 2 1814
傲寒
傲寒 2021-02-06 09:03

I want a generic zipWith function in C++ of variable arity. I have two problems. The first is that I cannot determine the type of the function pointer passed to zipWith. It must

2条回答
  •  轻奢々
    轻奢々 (楼主)
    2021-02-06 09:18

    Here is what I cobbled together:

    #include 
    #include 
    #include 
    
    template
    auto fold(F f, T&& t, Arg&& a) 
      -> decltype(f(std::forward(t), std::forward(a)))
    { return f(std::forward(t), std::forward(a)); }
    
    template
    auto fold(F f, T&& init, Head&& h, Args&&... args) 
      -> decltype(f(std::forward(init), std::forward(h)))
    { 
      return fold(f, f(std::forward(init), std::forward(h)), 
                  std::forward(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
      bool operator()(bool in, const std::pair& p) 
        { return in || p.first == p.second; }
    };
    
    // same same but different
    struct inc {
      template
      ignore operator()(ignore, std::pair& p) 
        { p.first++; return ignore(); }
    };
    
    template
    void zipWith(Fun f, OutputIterator out, 
                 std::pair... inputs) {
      if(fold(end_or(), false, inputs...)) return;
      while(!fold(end_or(), false, inputs...)) {
        *out++ = f( *(inputs.first)... );
        fold(inc(), ignore(), inputs...);
      }
    }
    
    template
    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
      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 a = {1, 2, 3}, b = {1, 2}, c = {1, 2, 3};
      vector 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;
    }
    

    This is a slightly improved variant over previous versions. As every good program should, it starts by defining a left-fold.

    It still does not solve the problem of iterators packed in pairs.

    In stdlib terms this function would be called transform and would require that only the length of one sequence is specified and the others be at least as long. I called it transformV here to avoid name clashes.

提交回复
热议问题