【问题标题】:Recursive variadic function template递归可变参数函数模板
【发布时间】:2017-08-22 20:24:45
【问题描述】:

我想编写一个类方法,它采用模板参数包,但参数为零,并“迭代”类型:

struct Bar {
    template <typename T, typename... Ts>
    void foo() {
        // something with T that involves Bar's members
        foo<Ts...>();
    }
};

实现这一点的首选方法是什么?

【问题讨论】:

    标签: c++ c++11 variadic-templates


    【解决方案1】:

    您可以使用以下内容:

    struct Bar {
        template <typename... Ts>
        void foo() {
            int dummy[] = {0 /*Manage case where Ts is empty*/,
                           (bar<Ts>(), void() /* To avoid overload `operator,` */, 0)...};
            (void) dummy; // suppress warning for unused variable.
        }
    
        template <typename T>
        void bar()
        {
            // something with T that involves Bar's members
        }
    
    };
    

    在 C++17 中,可以使用折叠表达式进行简化:

    struct Bar {
        template <typename... Ts>
        void foo() {
            (static_cast<void>(bar<Ts>()), ...);
        }
    
        template <typename T>
        void bar()
        {
            // something with T that involves Bar's members
        }
    
    };
    

    【讨论】:

      【解决方案2】:
      template<class...Fs>
      void do_in_order(Fs&&...fs) {
        int _[]={0, ( std::forward<Fs>(fs)(), void(), 0 )...};
        (void)_;
      }
      

      隐藏按从左到右的顺序执行一组函数对象所需的语法。

      然后:

      struct Bar {
        template <class... Ts>
        void foo() {
          do_in_order([&]{
            using T = Ts;
            // code 
          }...);
        }
      };
      

      在符合标准的编译器中,我们将运行// codeT 是从左到右的每种类型。

      请注意,一些自称是 C++11 编译器的编译器可能无法编译上述内容。

      这种技术的优点是它可以将讨厌的“扩展和评估模板”代码隐藏在具有清晰名称的函数中。您只需编写一次do_in_order,它通常就足以满足该数组扩展技巧的几乎所有用途。

      使用这种深奥的语法而不是“更简单”的递归解决方案有两个重要原因。

      首先,它使优化器的工作变得更容易。优化器有时会在一堆递归调用后放弃。

      其次,传统递归函数的函数签名的长度名称之和增长为 O(n^2)。如果使用辅助类型,名称的总长度也是 O(n^2)。除非您小心,否则这可能会导致编译时间、链接时间和二进制大小膨胀。

      在 C++1z 中,有一些“折叠”语法的计划,可能会使上述深奥的部分不那么深奥。

      【讨论】:

      • 这很整洁。显然,gcc 4.9.2 是那些无法编译它的“自称是 C++11 编译器的编译器”之一。
      • @Barry 是的。我认为 Clang 有效。出于某种原因,gcc 认为语句末尾的未扩展参数包是错误的。愚蠢的 gcc:语句可能只是扩展模式的一部分。
      【解决方案3】:

      我喜欢重载函数和使用类型列表:

      #include <iostream>
      #include <typeinfo>
      
      template <typename ...Ts> struct typelist { };
      
      void foo_impl(typelist<> )
      {
        // we are finished
      }
      
      template <typename T, typename ...Ts>
      void foo_impl(typelist<T, Ts...> )
      {
        std::cout << typeid(T).name() << ", ";
        foo_impl(typelist<Ts...>{});
      }
      
      
      
      template <typename ...Ts>
      void foo()
      {
        std::cout << "called with <";
        foo_impl(typelist<Ts...>{});
        std::cout << ">" << std::endl;
      }
      
      
      int main()
      {
        foo<int, char, float>();
      }
      

      【讨论】:

      • 嗯? foo 不接受任何参数。
      • 作为一个缺点,它需要 N 个类型的递归步骤,并实例化名称长度总和为 O(N^2) 的类型和函数。对 N 执行上述操作会导致编译速度变慢,如果编译器不剥离这些名称,可能会导致二进制膨胀。
      猜你喜欢
      • 1970-01-01
      • 2017-02-23
      • 1970-01-01
      • 2013-08-04
      • 2012-05-17
      • 2016-12-11
      • 1970-01-01
      • 1970-01-01
      • 2011-06-29
      相关资源
      最近更新 更多