最好的STL变换 - 就像三元运算符的模板函数一样

时间:2014-02-13 15:38:55

标签: c++ algorithm stl

STL定义了transform函数的两种风格

第一个是对于一元运算符:

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

第二个是二元运算符:

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

三元运算符的类似函数的最有效实现是什么?

修改: 这是我提出的简单实现,但是不是更简洁,更优雅的解决方案吗?

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;
}

2 个答案:

答案 0 :(得分:3)

可以实现这个的简单版本来创建像这样的n元变换:

    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;
    }

此版本尽可能保持与现有transform尽可能接近,使用一对first / last对迭代器,其余只是first s。这使得用户可以确保所有范围都有效,就像二进制变换一样。

至于表现,我同意ShighShagh关于表现的评论不太可能成为问题。编译器将比你更好地确定要采取哪些优化,因为每个实例化都可能导致程序员在编写此函数时无法知道的不同情况。

答案 1 :(得分:1)

这是我的看法。我有点被带走了,这导致了N维的变换函数:

#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;
}

用法很简单:

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;
}

ideone上的输出。

请注意,此版本适用于容器或不同大小,因此如果您知道容器的大小始终相同,则可以编辑detail::eq_n以仅检查第一个开始/结束迭代器是否相等。