【问题标题】:Best STL transform - like template function for ternary operators最佳 STL 转换 - 类似于三元运算符的模板函数
【发布时间】:2014-02-13 15:38:55
【问题描述】:

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

【问题讨论】:

  • 您的链接提供的示例有什么问题?只需将二元运算替换为三元运算即可。
  • @knivil 我猜 cplusplus.com 的实现是为了演示而非效率目的。
  • *result++ = ternary_op(*first1++, *first2++, *first3++); 短了一行 ;) (虽然可以说可读性较差)
  • @zr。我认为您的“简单实现”很棒。我怀疑您对效率的担忧是没有根据的——您是否进行了基准测试?仅供参考,隐藏循环(使用 STL 或其他方式)不会神奇地让您的程序更快。
  • transform(first1, last1, result, [&amp;first2, &amp;first3, &amp;ternary_op](decltype(*first1) f1) { return ternary_op(f1, *first2++, *first3++); }); 没有显式循环,但真的很丑

标签: c++ algorithm stl


【解决方案1】:

可以实现一个简单的版本来创建一个像这样的 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 迭代器,其余的只是firsts。这留给用户确保所有范围都有效,就像二进制转换一样。

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

【讨论】:

    【解决方案2】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-07-13
      • 2011-03-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-17
      • 2017-05-05
      相关资源
      最近更新 更多