首页 文章

如何迭代打包的可变参数模板参数列表?

提问于
浏览
39

我正在尝试找到一个迭代包变量模板参数列表的方法 . 现在和所有迭代一样,您需要某种方法来了解打包列表中有多少参数,更重要的是如何从打包参数列表中单独获取数据 .

一般的想法是迭代列表,将int类型的所有数据存储到向量中,将char *类型的所有数据存储到向量中,并将float类型的所有数据存储到向量中 . 在这个过程中,还需要一个单独的向量来存储参数进入顺序的各个字符 . 例如,当你使用push_back(a_float)时,你也在做一个push_back('f'),它只是存储一个单独的char来了解数据的顺序 . 我也可以在这里使用std :: string,只需使用= . 该矢量仅用作示例 .

现在,事物的设计方式是功能本身是使用宏构建的,尽管存在恶意,但它是必需的,因为这是一个实验 . 因此,使用递归调用几乎是不可能的,因为将容纳所有这些的实际实现将在编译时扩展;你不能招募一个宏 .

尽管有各种可能的尝试,我仍然坚持要弄清楚如何实际做到这一点 . 因此,我使用了一个更复杂的方法,包括构造一个类型,并将该类型传递到varadic模板,在向量中扩展它,然后简单地迭代它 . 但是我不想像以下那样调用函数:

foo(arg(1), arg(2.0f), arg("three");

所以真正的问题是如果没有这样的话我该怎么办?为了让你们更好地理解代码实际在做什么,我已经粘贴了我目前正在使用的乐观方法 .

struct any {
  void do_i(int   e) { INT    = e; }
  void do_f(float e) { FLOAT  = e; }
  void do_s(char* e) { STRING = e; }

  int   INT;
  float FLOAT;
  char *STRING;
};


template<typename T> struct get        { T      operator()(const any& t) { return T();      } };
template<>           struct get<int>   { int    operator()(const any& t) { return t.INT;    } };
template<>           struct get<float> { float  operator()(const any& t) { return t.FLOAT;  } };
template<>           struct get<char*> { char*  operator()(const any& t) { return t.STRING; } };

#define def(name)                                  \
  template<typename... T>                          \
  auto name (T... argv) -> any {                   \
   std::initializer_list<any> argin = { argv... }; \
    std::vector<any> args = argin;
#define get(name,T)  get<T>()(args[name])
#define end }

any arg(int   a) { any arg; arg.INT    = a; return arg; }
any arg(float f) { any arg; arg.FLOAT  = f; return arg; }
any arg(char* s) { any arg; arg.STRING = s; return arg; }

我知道这很讨厌,但这是一个纯粹的实验,不会在 生产环境 代码中使用 . 这纯粹是一个想法 . 它可能是一种更好的方式 . 但是您将如何使用此系统的示例:

def(foo)
  int data = get(0, int);
  std::cout << data << std::endl;
end

看起来很像python . 它也有效,但唯一的问题是你如何调用这个函数 . 下面是一个简单的例子:

foo(arg(1000));

我需要构建一个新的任何类型,这是非常美观的,但不是说那些宏也不是 . 除此之外,我只想做的选择:foo(1000);

我知道它可以完成,我只需要某种迭代方法,或者更重要的是一些std :: get方法用于打包的variadic模板参数列表 . 我确信可以做到 .

还要注意,我很清楚这不是完全类型友好的,因为我只支持int,float,char *,这对我来说没关系 . 我不需要任何其他东西,并且我将添加检查以使用type_traits来验证传递的参数确实是正确的,如果数据不正确则产生编译时错误 . 这纯粹不是问题 . 除了这些POD类型之外,我也不需要任何支持 .

如果我能得到一些建设性的帮助,反对关于我纯粹不合逻辑和愚蠢地使用宏和仅POD类型的争论,那将是非常高兴的 . 我很清楚代码是多么脆弱和破碎 . 这是merley的一个实验,我稍后可以解决非POD数据的问题,并使其更加类型安全和可用 .

感谢您的光临,我期待着您的帮助 .

7 回答

  • 16

    如果要将参数包装到 any ,可以使用以下设置 . 我还使 any 类更有用,尽管从技术上讲它不是 any 类 .

    #include <vector>
    #include <iostream>
    
    struct any {
      enum type {Int, Float, String};
      any(int   e) { m_data.INT    = e; m_type = Int;}
      any(float e) { m_data.FLOAT  = e; m_type = Float;}
      any(char* e) { m_data.STRING = e; m_type = String;}
      type get_type() const { return m_type; }
      int get_int() const { return m_data.INT; }
      float get_float() const { return m_data.FLOAT; }
      char* get_string() const { return m_data.STRING; }
    private:
      type m_type;
      union {
        int   INT;
        float FLOAT;
        char *STRING;
      } m_data;
    };
    
    template <class ...Args>
    void foo_imp(const Args&... args)
    {
        std::vector<any> vec = {args...};
        for (unsigned i = 0; i < vec.size(); ++i) {
            switch (vec[i].get_type()) {
                case any::Int: std::cout << vec[i].get_int() << '\n'; break;
                case any::Float: std::cout << vec[i].get_float() << '\n'; break;
                case any::String: std::cout << vec[i].get_string() << '\n'; break;
            }
        }
    }
    
    template <class ...Args>
    void foo(Args... args)
    {
        foo_imp(any(args)...);  //pass each arg to any constructor, and call foo_imp with resulting any objects
    }
    
    int main()
    {
        char s[] = "Hello";
        foo(1, 3.4f, s);
    }
    

    但是,可以编写函数来访问可变参数模板函数中的第n个参数,并将函数应用于每个参数,这可能是执行任何操作的更好方法 .

  • 20

    这不是通常使用Variadic模板的方式,根本不是 .

    根据语言规则,无法对可变参数包进行迭代,因此您需要转向递归 .

    class Stock
    {
    public:
      bool isInt(size_t i) { return _indexes.at(i).first == Int; }
      int getInt(size_t i) { assert(isInt(i)); return _ints.at(_indexes.at(i).second); }
    
      // push (a)
      template <typename... Args>
      void push(int i, Args... args) {
        _indexes.push_back(std::make_pair(Int, _ints.size()));
        _ints.push_back(i);
        this->push(args...);
      }
    
      // push (b)
      template <typename... Args>
      void push(float f, Args... args) {
        _indexes.push_back(std::make_pair(Float, _floats.size()));
        _floats.push_back(f);
        this->push(args...);
      }
    
    private:
      // push (c)
      void push() {}
    
      enum Type { Int, Float; };
      typedef size_t Index;
    
      std::vector<std::pair<Type,Index>> _indexes;
      std::vector<int> _ints;
      std::vector<float> _floats;
    };
    

    示例(在行动中),假设我们有 Stock stock;

    • stock.push(1, 3.2f, 4, 5, 4.2f); 被解析为(a)因为第一个参数是 int

    • this->push(args...) 扩展为 this->push(3.2f, 4, 5, 4.2f); ,解析为(b),因为第一个参数是 float

    • this->push(args...) 扩展为 this->push(4, 5, 4.2f); ,解析为(a)因为第一个参数是 int

    • this->push(args...) 扩展为 this->push(5, 4.2f); ,解析为(a),因为第一个参数是 int

    • this->push(args...) 扩展为 this->push(4.2f); ,解析为(b),因为第一个参数是 float

    • this->push(args...) 扩展为 this->push(); ,由于没有参数,因此解析为(c),从而结束递归

    从而:

    • 添加另一个要处理的类型就像添加另一个重载一样简单,更改第一个类型(例如, std::string const&

    • 如果传递完全不同的类型(例如 Foo ),则不能选择任何重载,从而导致编译时错误 .

    一个警告:自动转换意味着 double 将选择重载(b)和 short 选择重载(a) . 如果不需要,那么需要引入SFINAE,这使得该方法稍微复杂一些(至少它们的签名),例如:

    template <typename T, typename... Args>
    typename std::enable_if<is_int<T>::value>::type push(T i, Args... args);
    

    is_int 的位置如下:

    template <typename T> struct is_int { static bool constexpr value = false; };
    template <> struct is_int<int> { static bool constexpr value = true; };
    

    但是,另一种选择是考虑变体类型 . 例如:

    typedef boost::variant<int, float, std::string> Variant;
    

    它已经存在,有了所有实用程序,它可以存储在_685807中,复制等等......并且看起来非常像你需要的,即使它不使用Variadic模板 .

  • 27

    您可以通过在{}之间使用参数包初始化它来创建容器 . 只要params的类型是同质的或者至少可以转换为容器的元素类型,它就可以工作 . (用g 4.6.1测试)

    #include <array>
    
    template <class... Params>
    void f(Params... params) {
        std::array<int, sizeof...(params)> list = {params...};
    }
    
  • 3

    目前没有特定功能,但您可以使用一些解决方法 .

    使用初始化列表

    一种解决方法使用事实,initialization lists的子表达式按顺序进行评估 . int a[] = {get1(), get2()} 将在执行 get2 之前执行 get1 . 也许fold expressions将来会用于类似的技术 . 要在每个参数上调用 do() ,您可以执行以下操作:

    template <class... Args>
    void doSomething(Args... args) {
        int x[] = {args.do()...};
    }
    

    但是,这仅在 do() 返回 int 时有效 . 您可以使用comma operator来支持不返回正确值的操作 .

    template <class... Args>
    void doSomething(Args... args) {
        int x[] = {(args.do(), 0)...};
    }
    

    要做更复杂的事情,你可以把它们放在另一个函数中:

    template <class Arg>
    void process(Arg arg, int &someOtherData) {
        // You can do something with arg here.
    }
    
    template <class... Args>
    void doSomething(Args... args) {
        int someOtherData;
        int x[] = {(process(args, someOtherData), 0)...};
    }
    

    请注意,对于通用lambdas(C 14),您可以定义一个函数来为您执行此样板 .

    template <class F, class... Args>
    void do_for(F f, Args... args) {
        int x[] = {(f(args), 0)...};
    }
    
    template <class... Args>
    void doSomething(Args... args) {
        do_for([&](auto arg) {
            // You can do something with arg here.
        }, args...);
    }
    

    使用递归

    另一种可能性是使用递归 . 这是一个小例子,它定义了一个类似的函数 do_for ,如上所述 .

    template <class F, class First, class... Rest>
    void do_for(F f, First first, Rest... rest) {
        f(first);
        do_for(f, rest...);
    }
    template <class F>
    void do_for(F f) {
        // Parameter pack is empty.
    }
    
    template <class... Args>
    void doSomething(Args... args) {
        do_for([&](auto arg) {
            // You can do something with arg here.
        }, args...);
    }
    
  • 0

    你不能迭代,但你可以在列表上递归 . 检查维基百科上的printf()示例:http://en.wikipedia.org/wiki/C++0x#Variadic_templates

  • 7

    基于循环的范围很棒:

    #include <iostream>
    #include <any>
    
    template <typename... Things>
    void printVariadic(Things... things) {
        for(const auto p : {things...}) {
            std::cout << p.type().name() << std::endl;
        }
    }
    
    int main() {
        printVariadic(std::any(42), std::any('?'), std::any("C++"));
    }
    

    对我来说,this产生输出:

    i
    c
    PKc
    

    Here是一个没有 std::any 的例子,对于那些不熟悉 std::type_info 的人来说可能更容易理解:

    #include <iostream>
    
    template <typename... Things>
    void printVariadic(Things... things) {
        for(const auto p : {things...}) {
            std::cout << p << std::endl;
        }
    }
    
    int main() {
        printVariadic(1, 2, 3);
    }
    

    正如您所料,这会产生:

    1
    2
    3
    
  • 3

    你可以使用多个可变参数模板,这有点乱,但它的工作原理很容易理解 . 你只需要一个带有可变参数模板的函数,如下所示:

    template <typename ...ArgsType >
    void function(ArgsType... Args){
         helperFunction(Args...);
    }
    

    还有一个像这样的辅助函数:

    void helperFunction() {}
    
    template <typename T, typename ...ArgsType >
    void helperFunction(T t, ArgsType... Args) {
    
         //do what you want with t
        function(Args...);
    
    }
    

    现在,当您调用“function”时,将调用“helperFunction”并将第一个传递的参数与其余参数隔离,此变量可用于调用另一个函数(或其他内容) . 然后将一次又一次地调用“function”,直到不再有变量为止 . 注意,您可能必须在“function”之前声明helperClass .

    最终代码如下所示:

    void helperFunction();
    
    template <typename T, typename ...ArgsType >
    void helperFunction(T t, ArgsType... Args);
    
    template <typename ...ArgsType >
    void function(ArgsType... Args){
         helperFunction(Args...);
    }
    
    void helperFunction() {}
    
    template <typename T, typename ...ArgsType >
    void helperFunction(T t, ArgsType... Args) {
    
         //do what you want with t
        function(Args...);
    
    }
    

    代码未经过测试 .

相关问题