Best STL transform - like template function for ternary operators

可紊 提交于 2020-01-13 03:20:10

问题


STL defines two flavors of the transform function

The first is For unary operators:

template <class InputIterator, class OutputIterator, class UnaryOperation>
OutputIterator transform (InputIterator first1, InputIterator last1,
                                OutputIterator result, UnaryOperation op);

And the second is for binary operators:

template <class InputIterator1, class InputIterator2,
          class OutputIterator, class BinaryOperation>
  OutputIterator transform (InputIterator1 first1, InputIterator1 last1,
                            InputIterator2 first2, OutputIterator result,
                            BinaryOperation binary_op);

What is the most efficient implementation of a similiar function for a ternary operator?

EDIT: Here is the trivial implementation I came up with, but isn't there a leaner and more elegant solution?

template <class InputIterator1, class InputIterator2, class InputIterator3,
          class OutputIterator, class TrenaryOperation>
  OutputIterator transform3(InputIterator1 first1, InputIterator1 last1,
                            InputIterator2 first2, InputIterator3 first3, OutputIterator result,
                            TrenaryOperation trenary_op)
{
  while (first1 != last1) {
    *result = trenary_op(*first1, *first2, *first3);
    ++result; ++first1; ++first2; ++first3;
  }
  return result;
}

回答1:


A simple version of this can be achieved to create an n-ary transform like this:

    template <class Functor, class OutputIterator,
              class Input1, class ... Inputs>
    OutputIterator transform(Functor f, OutputIterator out,
                             Input1 first1, Input1 last1,
                             Inputs ... inputs)
    {
        while(first1 != last1)
            *out++ = f(*first1++, *inputs++...);
        return out;
    }

This version tries to stay as close to the existing transform as possible, taking one first/last pair of iterators and the rest are just firsts. This leaves it up to the user to make sure all the ranges are valid, just as with the binary-transform.

As for performance, I agree with ShighShagh's comment about performance not likely being an issue here. The compiler will be in a better place than you to determine what optimizations to take because each instantiation could lead to different situations that the programmer couldn't possibly know while writing this function.




回答2:


Here is my take at it. I got a little carried away, which resulted in a transform function for N dimensions:

#include <iostream>     // for std::cout
#include <iterator>     // for std::ostream_iterator
#include <tuple>        // for std::tie
#include <type_traits>  // for std::enable_if
#include <vector>       // for std::vector

template<typename T>
struct identity { using type = T; };

template<typename Integral, Integral... N>
struct integer_sequence {

  template<Integral Offset>
  struct offset : identity<integer_sequence<Integral, (N + Offset)...>> { };
};

namespace detail {

  template<typename... T>
  void ignore(T&&...) { }

  template<std::size_t Idx, typename... T>
  inline auto nth_arg(T&&... arg)
  -> decltype(std::get<Idx>(std::tie(arg...))) {
    return std::get<Idx>(std::tie(arg...));
  }

  template<std::size_t N, std::size_t... T>
  struct gen_iter_indices
  : gen_iter_indices<(N - 2), (N - 2), T...> { };
  template<std::size_t... T>
  struct gen_iter_indices<0, T...>
  : identity<integer_sequence<std::size_t, T...>> { };

  template<
    typename... Iterator,
    typename Integral,
    Integral... Begin,
    Integral... End
  >
  inline static bool eq_n(const std::tuple<Iterator...>& iters,
                          integer_sequence<Integral, Begin...>,
                          integer_sequence<Integral, End...>)
  {
    const bool res[] { (std::get<Begin>(iters) == std::get<End>(iters))... };
    for(std::size_t i = 0; i < sizeof...(Begin); ++i) {
      if(res[i]) { return true; }
    }
    return false;
  }

  template<typename... Iterator, typename Integral, Integral... Begin>
  inline static void increment_n(const std::tuple<Iterator...>& iters,
                                 integer_sequence<Integral, Begin...>)
  {
    ignore(++std::get<Begin>(iters)...);
  }

  template<
    typename NaryOperation,
    typename... Iterator,
    typename Integral,
    Integral... Begin
  >
  inline auto call_n(const std::tuple<Iterator...>& iters,
                     NaryOperation op,
                     integer_sequence<Integral, Begin...>)
  -> decltype(op(*std::get<Begin>(iters)...))
  {
    return op(*std::get<Begin>(iters)...);
  }
}

template<
  typename OutputIter,
  typename NaryOperation,
  typename... InputIter,
  typename =  typename std::enable_if<
                (2 <= sizeof...(InputIter)) &&    // Atleast one iterator pair
                (0 == (sizeof...(InputIter) % 2)) // and multiple of two
              >::type
>
static OutputIter transform_n(OutputIter out_iter,
                              NaryOperation op,
                              InputIter... in_iter)
{
  using begins = typename detail::gen_iter_indices<sizeof...(InputIter)>::type;
  using ends = typename begins::template offset<1>::type;

  const auto iters = std::tie(in_iter...); // tuple of references to iterators

  while(!detail::eq_n(iters, begins{}, ends{})) {
    *out_iter = detail::call_n(iters, op, begins{});
    ++out_iter;
    detail::increment_n(iters, begins{});
  }

  return out_iter;
}

Usage is simple:

int main(int argc, char** argv) {
  std::vector<int> v1 { 1, 2, 3 };
  std::vector<int> v2 { 4, 5, 6 };
  std::vector<int> v3 { 7, 8, 9 };
  std::vector<int> res { };
  res.resize(3);
  auto end = transform_n(
    res.begin(),
    [](int _1, int _2, int _3) { return _1 + _2 + _3; },
    v1.begin(),
    v1.end(),
    v2.begin(),
    v2.end(),
    v3.begin(),
    v3.end()
  );
  std::copy(res.begin(), end, std::ostream_iterator<int>(std::cout, " "));
  return 0;
}

Output on ideone.

Note that in this version works with containers or different sizes, so if you know your containers will always be the same size, you can edit detail::eq_n to only check the first begin/end iterators for equality.



来源:https://stackoverflow.com/questions/21758718/best-stl-transform-like-template-function-for-ternary-operators

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!