如何遍历打包的可变参数模板参数列表?
Posted
技术标签:
【中文标题】如何遍历打包的可变参数模板参数列表?【英文标题】:How can I iterate over a packed variadic template argument list? 【发布时间】:2011-11-06 00:49:35 【问题描述】:我正在尝试找到一种方法来迭代一个包可变参数模板参数列表。 现在与所有迭代一样,您需要某种方法来了解打包列表中有多少参数,更重要的是如何从打包参数列表中单独获取数据。
总体思路是遍历列表,将所有int类型的数据存入vector,将char*类型的所有数据存入vector,将float类型的所有数据存入vector。在这个过程中,还需要一个单独的向量来存储参数输入顺序的各个字符。例如,当你 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
看起来很像python。它也可以,但唯一的问题是你如何调用这个函数。 下面是一个简单的例子:
foo(arg(1000));
我需要构造一个新的 any 类型,它非常美观,但这并不是说这些宏也不是。顺便说一句,我只想选择这样做: 富(1000);
我知道可以做到,我只需要某种迭代方法,或者更重要的是一些用于打包可变参数模板参数列表的 std::get 方法。我确信可以做到。
另外需要注意的是,我很清楚这并不完全是类型友好的,因为我只支持 int、float、char* ,这对我来说没问题。我不需要其他任何东西,我将添加检查以使用 type_traits 来验证传递的参数确实是正确的,以便在数据不正确时产生编译时错误。这完全不是问题。除了这些 POD 类型之外,我也不需要任何支持。
如果我能得到一些建设性的帮助,我将不胜感激,反对关于我纯粹不合逻辑和愚蠢地使用宏和仅 POD 类型的争论。我很清楚代码是多么脆弱和破碎。这是 merley 的一项实验,我稍后可以纠正非 POD 数据的问题,并使其更加类型安全和可用。
感谢您的理解,我期待提供帮助。
【问题讨论】:
看起来有点像你可以改用boost::variant<int, float, std::string>
。但是,这将只允许一次为每个“任何”存储一个值,并提供检查存储值类型的方法。
我不想使用 boost。尽管其他人似乎喜欢它提供的大量支持,但我只是试图坚持标准库中支持的内容。
【参考方案1】:
您不能迭代,但可以递归遍历列表。查看***上的 printf() 示例:http://en.wikipedia.org/wiki/C++0x#Variadic_templates
【讨论】:
是的,我明白了,但我仍然需要一种方法来了解发生这种情况的顺序,并且可以选择按照放入数据的相同顺序重建数据。【参考方案2】:这不是人们通常使用可变参数模板的方式,根本不是。
根据语言规则,不可能对可变参数包进行迭代,因此您需要转向递归。
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;
它已经存在,与所有实用程序一起使用,它可以存储在vector
中、复制等等......看起来真的很像您需要的东西,即使它不使用可变参数模板。
【讨论】:
这不是我要找的,因为这只能让我 push(a_bunch_of_ints);或推(a_bunch_of_floats);我正在寻找的是 push(a_bunch_of_ints_and_floats_in_any_order); @graphitemaster:恐怕你不明白可变参数模板(或重载解析)是如何工作的。我已经更新了一个示例,以表明 它确实 可以按您的预期工作。【参考方案3】:如果要将参数包装到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 个参数并将一个函数应用于每个参数,这可能是一种更好的方式来做任何你想做的事情。
【讨论】:
这实际上很简洁,实际上是我正在寻找的,我可以修改它以使其可用。 我尝试在 lambda 函数中使用std::vector<any> vec = args...;
- 我想像在你的示例中那样处理参数,但是它不会编译 - 你知道这个表达式是否仅适用于模板函数吗?
typedef boost::variant您可以通过在 之间使用参数包对其进行初始化来创建它的容器。只要 params... 的类型是同质的或至少可以转换为容器的元素类型,它就可以工作。 (使用 g++ 4.6.1 测试)
#include <array>
template <class... Params>
void f(Params... params)
std::array<int, sizeof...(params)> list = params...;
【讨论】:
欢迎使用 SO,发布完整编译示例是个好主意。由于解决方案对您来说可能很明显,这里有些人对某些语言不熟悉,对他们来说并不那么明显。 :) 我还建议您查看我们的常见问题解答:***.com/faq 也可以使用std::tuple来支持异构类型,比如这个答案http://***.com/a/15139244/2607949【参考方案5】:目前没有针对它的特定功能,但您可以使用一些解决方法。
使用初始化列表
一种解决方法是利用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...);
【讨论】:
【参考方案6】:您可以使用多个可变参数模板,这有点乱,但它有效且易于理解。 您只需拥有一个带有可变参数模板的函数,如下所示:
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”并将第一个传递的参数与其余参数隔离开来,这个变量可以用来调用另一个函数(或其他东西)。然后“函数”将被一次又一次地调用,直到没有更多的变量。请注意,您可能必须在“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...);
代码未经测试。
【讨论】:
【参考方案7】:基于范围的 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++"));
对我来说,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
【讨论】:
这只是因为编译器可以优化你的同构初始化列表。这并非在所有情况下都有效。 @Anthony,是的,我看到这不适用于混合类型的输入,这真是太可惜了......【参考方案8】:如果您的输入都是同一类型,请参阅 OMGtechy
的最佳答案。
对于混合类型,我们可以将fold expressions(在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);
Live demo
如果你想要perfect forwarding,改成:
(auto && input) // double "&" now
// ...
(std::forward<T>(multi_inputs)), ...);
如果您的循环中需要return
/break
s,这里有一些解决方法:
throw
s can cause tremendous slow down这个函数;因此,仅在速度不重要或 break
/return
s 真正异常时才使用此选项。
Demo using variable/if switches。
这些后面的答案老实说是代码味道,但表明它是通用的。
【讨论】:
这真是简洁、紧凑、易懂! 如果您在尝试理解此上下文中的 lambda 语法时遇到问题,这意味着:***.com/questions/68872572/… 为了更好的可读性,我建议在折叠表达式之外定义 lambda。auto processOne = [&]() ... ; (processOne(multi_inputs), ...);
对于您的模板仿射程度较低的同事来说可能更容易解析,您可以更轻松地对其进行评论:godbolt.org/z/4GhrGPqbT【参考方案9】:
#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
【讨论】:
以上是关于如何遍历打包的可变参数模板参数列表?的主要内容,如果未能解决你的问题,请参考以下文章