最佳 STL 变换 - 类似于三元运算符的模板函数



STL 定义了变换函数的两种风格

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

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

可以实现一个简单的版本来创建如下所示的 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秒。 这让用户负责确保所有范围都有效,就像二进制转换一样。

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

这是我

的看法。我有点得意忘形,这导致了 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以仅检查第一个开始/结束迭代器是否相等。

最新更新