【问题标题】:Why are function calls in this parameter pack evaluated backwards?为什么这个参数包中的函数调用是向后评估的?
【发布时间】:2021-11-02 00:02:33
【问题描述】:

最近我发现this StackOverflow answer 关于使用模板展开循环。答案说“这个想法适用于 C++11”,我最终得到了这个:

namespace tmpl {
  namespace details {
    template<class T, T... values>
    class integer_sequence {
    public:
      static constexpr size_t size() { return sizeof...(values); }
    };
    
    template<class T, class N, class... Is>
    struct make_integer_sequence_helper :
      make_integer_sequence_helper<T, std::integral_constant<T, N::value - 1>, std::integral_constant<T, N::value - 1>, Is...> {};
      
    template<class T, class... Is>
    struct make_integer_sequence_helper<T, std::integral_constant<T, 0>, Is...> {
      using type = integer_sequence<T, Is::value...>;
    };
    
    template<class T, T N>
    using make_integer_sequence = typename make_integer_sequence_helper<T, std::integral_constant<T, N>>::type;
    
    template<class... Ts>
    void variadic_noop(Ts... params) {}
    
    template<class F, class T>
    int call_and_return_0(F&& f, T i) {f(i); return 0;}
    
    template<class T, T... Is, class F>
    void loop(integer_sequence<T, Is...>, F&& f) {
      variadic_noop(call_and_return_0(f, Is)...);
    }
  }
  template<class T, T max, class F>
  void loop(F&& f) {
    details::loop(details::make_integer_sequence<T, max>{}, f);
  }
}

让我们举一个简单的例子来说明如何使用这个模板:

tmpl::loop<size_t, 20>([&](size_t idx) {
  cout << "Loop " << idx << std::endl;
});

当我使用另一个答案中的 C++17 代码时,它会从 0 迭代到 19。但是,我编写的 C++11 jank 从 19 迭代到 0。

理论上,当details::loop() 展开时,它应该变成这样:

variadic_noop(call_and_return_0(f, 0), call_and_return_0(f, 1), call_and_return_0(f, 2), ...);

那么,如果 call_and_return_0(f, 19) 是 variadic_noop() 的最后一个参数,为什么 C++ 会先运行它?

【问题讨论】:

    标签: c++ for-loop template-meta-programming


    【解决方案1】:

    函数调用中函数参数的求值可能是从左到右、从右到左或任何其他顺序,这不需要是可预测的。

    但是,当您有一个e_1, e_2, ..., e_n 形式的单个表达式,其中子表达式e_1、e_2、...、e_n 由逗号运算符分隔,则子表达式将始终按从左到右的顺序求值,因为逗号运算符保证其左操作数在其右操作数之前求值。 (在 C++17 之前,此保证仅适用于内置逗号运算符,不适用于任何重载的逗号运算符。)

    虽然函数调用也使用逗号,但函数调用中的逗号并不强制执行从左到右的计算。链接答案中的代码使用 C++17 折叠表达式创建由逗号分隔的表达式序列运算符。

    【讨论】:

      【解决方案2】:

      加上 Brian 所说的,可靠地获得订单的唯一方法是将循环编写为递归模板。示例实现:

          template <class T, T I, T... Is, class F>
          void loop(integer_sequence<T, I, Is...>, F&& f) {
            f(I);
            loop(integer_sequence<T, Is...> {}, f);
          }
          
          template<class T, class F>
          void loop(integer_sequence<T>, F&& f) {}
      

      编辑:我再次查看了包扩展发生的位置,以及this StackOverflow answer。为了回避函数求值顺序问题,您可以使用花括号初始化器列表,它保证从左到右的顺序。它还将允许更大的序列,因为不再需要递归来展开循环。

          inline void init_list_noop(std::initializer_list<int>) {}
      
          template<class T, T val, class F>
          int call_and_return_0(F&& f) {
            f(val);
            return 0;
          }
      
          template<class T, T... vals, class F>
          void loop(integer_sequence<T, vals...>, F&& f) {
            init_list_noop({call_and_return_0<T, vals>(f)...});
          }
      

      【讨论】:

        猜你喜欢
        • 2020-06-07
        • 2012-02-03
        • 1970-01-01
        • 1970-01-01
        • 2019-06-21
        • 1970-01-01
        • 2023-03-03
        • 2012-09-13
        相关资源
        最近更新 更多