67

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

总体思路是遍历列表,将所有 int 类型的数据存储到一个向量中,将所有 char* 类型的数据存储到一个向量中,并将所有浮点类型的数据存储到一个向量中。在此过程中,还需要一个单独的向量来存储参数输入顺序的各个字符。例如,当您 push_back(a_float) 时,您也在执行 push_back('f'),它只是存储一个单独的字符来了解数据的顺序。我也可以在这里使用 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

看起来很像蟒蛇。它也可以,但唯一的问题是你如何调用这个函数。这是一个简单的例子:

foo(arg(1000));

我需要构建一个新的 any 类型,它非常美观,但这并不是说这些宏也不是。顺便说一句,我只想选择这样做: foo(1000);

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

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

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

感谢您的理解,我期待提供帮助。

4

9 回答 9

37

如果要将参数包装到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 个参数并将函数应用于每个参数,这可能是做任何你想要实现的更好的方法。

于 2011-08-29T16:30:45.117 回答
25

如果您的输入都是相同的类型,请参阅OMGtechy's great answer。

对于混合类型,我们可以将折叠表达式(在 中引入c++17)与可调用对象(在本例中为lambda)一起使用:

#include <iostream>

template <typename ... Ts>
void Foo (Ts && ... multi_inputs)
{
    int i = 0;
    
    ([&] (auto & input)
    {
        // Do things in your "loop" lambda
    
        ++i;
        std::cout << "input " << i << " = " << input << std::endl;

    } (multi_inputs), ...);
}

int main ()
{
    Foo(2, 3, 4u, (int64_t) 9, 'a', 2.3);
}

现场演示

如果要完美转发,改成:

(auto && input) // double "&" now

// ...

(std::forward<T>(multi_inputs)), ...);

如果您的循环中需要return/ breaks,这里有一些解决方法:

老实说,后面的这些答案是代码味道,但表明它是通用的。

于 2020-02-09T12:44:26.833 回答
24

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

#include <array>

template <class... Params>
void f(Params... params) {
    std::array<int, sizeof...(params)> list = {params...};
}
于 2012-02-13T14:15:11.927 回答
22

这不是人们通常使用可变参数模板的方式,根本不是。

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

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),则无法选择重载,从而导致编译时错误。

一个警告:自动转换意味着 adouble将选择重载 (b) 并且 ashort将选择重载 (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;

它已经存在,与所有实用程序一起,它可以存储在 a 中vector,复制,等等......并且看起来真的很像你需要的,即使它不使用可变参数模板。

于 2011-08-29T13:39:24.000 回答
20

目前没有针对它的特定功能,但您可以使用一些解决方法。

使用初始化列表

一种解决方法是使用初始化列表的子表达式按顺序计算的事实。int a[] = {get1(), get2()}将在执行get1之前执行get2。也许折叠表达式将来会为类似的技术派上用场。要调用do()每个参数,您可以执行以下操作:

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

但是,这仅在do()返回int. 您可以使用逗号运算符来支持不返回正确值的操作。

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...);
}
于 2016-05-14T13:33:44.417 回答
20

基于范围的 for 循环非常棒:

#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++"));
}

对我来说,会产生输出:

i
c
PKc

是一个没有 的示例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
于 2018-06-16T23:41:34.237 回答
4

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

于 2011-08-29T13:22:14.760 回答
1

您可以使用多个可变参数模板,这有点乱,但它有效且易于理解。您只需使用带有可变参数模板的函数,如下所示:

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...);

}

现在,当您调用“函数”时,将调用“helperFunction”并将第一个传递的参数与其余参数隔离开来,这个变量可以用来调用另一个函数(或其他东西)。然后“函数”将被一次又一次地调用,直到没有更多的变量。请注意,您可能必须在“函数”之前声明 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...);

}

代码未经测试。

于 2017-09-26T20:18:46.430 回答
0
#include <iostream>    

template <typename Fun>
void iteratePack(const Fun&) {}

template <typename Fun, typename Arg, typename ... Args>
void iteratePack(const Fun &fun, Arg &&arg, Args&& ... args)
{
    fun(std::forward<Arg>(arg));
    iteratePack(fun, std::forward<Args>(args)...);
}

template <typename ... Args>
void test(const Args& ... args)
{
    iteratePack([&](auto &arg)
    {
        std::cout << arg << std::endl;
    },
    args...);
}

int main()
{
    test(20, "hello", 40);

    return 0;
}

输出:

20
hello
40
于 2020-09-22T13:33:49.810 回答