C++ 如何遍历打包的可变参数模板参数列表?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/7230621/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How can I iterate over a packed variadic template argument list?
提问by graphitemaster
I'm trying to find a method to iterate over an a pack variadic template argument list. Now as with all iterations, you need some sort of method of knowing how many arguments are in the packed list, and more importantly how to individually get data from a packed argument list.
我试图找到一种方法来迭代一个包可变参数模板参数列表。现在与所有迭代一样,您需要某种方法来了解压缩列表中有多少个参数,更重要的是如何从压缩参数列表中单独获取数据。
The general idea is to iterate over the list, store all data of type int into a vector, store all data of type char* into a vector, and store all data of type float, into a vector. During this process there also needs to be a seperate vector that stores individual chars of what order the arguments went in. As an example, when you push_back(a_float), you're also doing a push_back('f') which is simply storing an individual char to know the order of the data. I could also use a std::string here and simply use +=. The vector was just used as an example.
总体思路是遍历列表,将所有 int 类型的数据存储到一个向量中,将所有 char* 类型的数据存储到一个向量中,并将所有 float 类型的数据存储到一个向量中。在这个过程中,还需要一个单独的向量来存储参数进入的顺序的各个字符。 例如,当你 push_back(a_float) 时,你也在做一个 push_back('f'),它只是存储一个单独的字符来了解数据的顺序。我也可以在这里使用 std::string 并简单地使用 +=。该向量仅用作示例。
Now the way the thing is designed is the function itself is constructed using a macro, despite the evil intentions, it's required, as this is an experiment. So it's literally impossible to use a recursive call, since the actual implementation that will house all this will be expanded at compile time; and you cannot recruse a macro.
现在设计的方式是函数本身是使用宏构造的,尽管有恶意,但这是必需的,因为这是一个实验。所以实际上不可能使用递归调用,因为包含所有这些的实际实现将在编译时扩展;并且您不能重新使用宏。
Despite all possible attempts, I'm still stuck at figuring out how to actually do this. So instead I'm using a more convoluted method that involves constructing a type, and passing that type into the varadic template, expanding it inside a vector and then simply iterating that. However I do not want to have to call the function like:
尽管进行了所有可能的尝试,但我仍然坚持弄清楚如何实际做到这一点。因此,我使用了一种更复杂的方法,该方法涉及构造一个类型,然后将该类型传递到 varadic 模板中,将其扩展到一个向量中,然后简单地对其进行迭代。但是我不想像这样调用函数:
foo(arg(1), arg(2.0f), arg("three");
So the real question is how can I do without such? To give you guys a better understanding of what the code is actually doing, I've pasted the optimistic approach that I'm currently using.
所以真正的问题是我怎么能没有这样的?为了让你们更好地理解代码实际上在做什么,我粘贴了我目前使用的乐观方法。
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; }
I know this is nasty, however it's a pure experiment, and will not be used in production code. It's purely an idea. It could probably be done a better way. But an example of how you would use this system:
我知道这很糟糕,但它是一个纯粹的实验,不会在生产代码中使用。这纯粹是一个想法。它可能可以做得更好。但是,您将如何使用此系统的示例:
def(foo)
int data = get(0, int);
std::cout << data << std::endl;
end
looks a lot like python. it works too, but the only problem is how you call this function. Heres a quick example:
看起来很像蟒蛇。它也可以工作,但唯一的问题是您如何调用此函数。这是一个快速示例:
foo(arg(1000));
I'm required to construct a new any type, which is highly aesthetic, but thats not to say those macros are not either. Aside the point, I just want to the option of doing: foo(1000);
我需要构建一个新的 any 类型,它非常美观,但这并不是说那些宏也不是。抛开这一点,我只想选择做: foo(1000);
I know it can be done, I just need some sort of iteration method, or more importantly some std::get method for packed variadic template argument lists. Which I'm sure can be done.
我知道它可以完成,我只需要某种迭代方法,或者更重要的是一些用于打包可变参数模板参数列表的 std::get 方法。我相信这是可以做到的。
Also to note, I'm well aware that this is not exactly type friendly, as I'm only supporting int,float,char* and thats okay with me. I'm not requiring anything else, and I'll add checks to use type_traits to validate that the arguments passed are indeed the correct ones to produce a compile time error if data is incorrect. This is purely not an issue. I also don't need support for anything other then these POD types.
还要注意的是,我很清楚这并不完全是类型友好的,因为我只支持 int、float、char*,这对我来说没问题。我不需要其他任何东西,我将添加检查以使用 type_traits 来验证传递的参数确实是正确的参数,如果数据不正确,则会产生编译时错误。这完全不是问题。除了这些 POD 类型之外,我也不需要任何其他支持。
It would be highly apprecaited if I could get some constructive help, opposed to arguments about my purely illogical and stupid use of macros and POD only types. I'm well aware of how fragile and broken the code is. This is merley an experiment, and I can later rectify issues with non-POD data, and make it more type-safe and useable.
如果我能得到一些建设性的帮助,我将非常感激,反对关于我纯粹不合逻辑和愚蠢地使用宏和 POD 类型的争论。我很清楚代码是多么脆弱和破碎。这只是一个实验,我以后可以纠正非 POD 数据的问题,并使其更类型安全和可用。
Thanks for your undertstanding, and I'm looking forward to help.
感谢您的理解,我期待着提供帮助。
采纳答案by UncleBens
If you want to wrap arguments to any
, you can use the following setup. I also made the any
class a bit more usable, although it isn't technically an any
class.
如果要将参数包装到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);
}
It is however possible to write functions to access the nth argument in a variadic template function and to apply a function to each argument, which might be a better way of doing whatever you want to achieve.
然而,可以编写函数来访问可变参数模板函数中的第 n 个参数,并将函数应用于每个参数,这可能是实现您想要实现的任何目标的更好方法。
回答by Matthieu M.
This is not how one would typically use Variadic templates, not at all.
这不是人们通常使用可变参数模板的方式,根本不是。
Iterations over a variadic pack is not possible, as per the language rules, so you need to turn toward recursion.
根据语言规则,不可能对可变参数包进行迭代,因此您需要转向递归。
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;
};
Example (in action), suppose we have Stock stock;
:
示例(在行动中),假设我们有Stock stock;
:
stock.push(1, 3.2f, 4, 5, 4.2f);
is resolved to (a) as the first argument is anint
this->push(args...)
is expanded tothis->push(3.2f, 4, 5, 4.2f);
, which is resolved to (b) as the first argument is afloat
this->push(args...)
is expanded tothis->push(4, 5, 4.2f);
, which is resolved to (a) as the first argument is anint
this->push(args...)
is expanded tothis->push(5, 4.2f);
, which is resolved to (a) as the first argument is anint
this->push(args...)
is expanded tothis->push(4.2f);
, which is resolved to (b) as the first argument is afloat
this->push(args...)
is expanded tothis->push();
, which is resolved to (c) as there is no argument, thus ending the recursion
stock.push(1, 3.2f, 4, 5, 4.2f);
被解析为 (a) 因为第一个参数是int
this->push(args...)
扩展为this->push(3.2f, 4, 5, 4.2f);
,解析为 (b),因为第一个参数是 afloat
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),因为第一个参数是 afloat
this->push(args...)
扩展为this->push();
,由于没有参数,因此解析为 (c),从而结束递归
Thus:
因此:
- Adding another type to handle is as simple as adding another overload, changing the first type (for example,
std::string const&
) - If a completely different type is passed (say
Foo
), then no overload can be selected, resulting in a compile-time error.
- 添加另一个类型来处理就像添加另一个重载一样简单,更改第一个类型(例如,
std::string const&
) - 如果传递了完全不同的类型(例如
Foo
),则无法选择重载,从而导致编译时错误。
One caveat: Automatic conversion means a double
would select overload (b) and a short
would select overload (a). If this is not desired, then SFINAE need be introduced which makes the method slightly more complicated (well, their signatures at least), example:
一个警告:自动转换意味着 adouble
将选择重载 (b),而 ashort
将选择重载 (a)。如果不需要,则需要引入 SFINAE,这会使方法稍微复杂一些(好吧,至少是它们的签名),例如:
template <typename T, typename... Args>
typename std::enable_if<is_int<T>::value>::type push(T i, Args... args);
Where is_int
would be something like:
哪里is_int
会是这样的:
template <typename T> struct is_int { static bool constexpr value = false; };
template <> struct is_int<int> { static bool constexpr value = true; };
Another alternative, though, would be to consider a variant type. For example:
不过,另一种选择是考虑变体类型。例如:
typedef boost::variant<int, float, std::string> Variant;
It exists already, with all utilities, it can be stored in a vector
, copied, etc... and seems really much like what you need, even though it does not use Variadic Templates.
它已经存在,具有所有实用程序,它可以存储在vector
、复制等中...并且看起来非常像您需要的东西,即使它不使用可变模板。
回答by karel
You can create a container of it by initializing it with your parameter pack between {}. As long as the type of params... is homogeneous or at least convertable to the element type of your container, it will work. (tested with g++ 4.6.1)
您可以通过使用 {} 之间的参数包对其进行初始化来创建它的容器。只要 params... 的类型是同构的或至少可以转换为容器的元素类型,它就会起作用。(用 g++ 4.6.1 测试)
#include <array>
template <class... Params>
void f(Params... params) {
std::array<int, sizeof...(params)> list = {params...};
}
回答by JojOatXGME
There is no specific feature for it right now but there are some workarounds you can use.
目前没有针对它的特定功能,但您可以使用一些解决方法。
Using initialization list
使用初始化列表
One workaround uses the fact, that subexpressions of initialization listsare evaluated in order. int a[] = {get1(), get2()}
will execute get1
before executing get2
. Maybe fold expressionswill come handy for similar techniques in the future. To call do()
on every argument, you can do something like this:
一种解决方法使用了这样一个事实,即初始化列表的子表达式是按顺序计算的。int a[] = {get1(), get2()}
将在执行get1
之前执行get2
。也许折叠表达式将来会在类似技术中派上用场。要调用do()
每个参数,您可以执行以下操作:
template <class... Args>
void doSomething(Args... args) {
int x[] = {args.do()...};
}
However, this will only work when do()
is returning an int
. You can use the comma operatorto support operations which do not return a proper value.
但是,这仅在do()
返回int
. 您可以使用逗号运算符来支持不返回正确值的操作。
template <class... Args>
void doSomething(Args... args) {
int x[] = {(args.do(), 0)...};
}
To do more complex things, you can put them in another function:
为了做更复杂的事情,你可以把它们放在另一个函数中:
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)...};
}
Note that with generic lambdas(C++14), you can define a function to do this boilerplate for you.
请注意,使用通用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...);
}
Using recursion
使用递归
Another possibility is to use recursion. Here is a small example that defines a similar function do_for
as above.
另一种可能性是使用递归。这是一个小例子,它定义了一个与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...);
}
回答by OMGtechy
Range based for loops are wonderful:
基于范围的 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++"));
}
For me, thisproduces the output:
对我来说,这会产生输出:
i
c
PKc
Here's an example without std::any
, which might be easier to understand for those not familiar with std::type_info
:
这是一个没有 的示例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);
}
As you might expect, this produces:
正如您所料,这会产生:
1
2
3
回答by Elliott
This is really an improvement on the answer by JojOatXGME(I figured this out by playing around with their answer). This worked best for me for mixed-type inputs without using external functions (c++17):
这确实是对JojOatXGME答案的改进(我通过玩弄他们的答案发现了这一点)。这对我来说最适合不使用外部函数的混合类型输入(c++17):
#include <iostream>
template <typename ... T>
void Foo (const T && ... inputs)
{
int i = 0;
auto loop = [&] (auto && input)
{
// Do things in your "loop" lambda
++i;
std::cout << "input " << i << " = " << input << std::endl;
};
(loop(inputs), ...);
}
int main()
{
Foo(2, 3, 4u, (int64_t) 9, 'a', 2.3);
return 0;
}
Outputs for g++ -std=c++17
:
输出g++ -std=c++17
:
input 1 = 2
input 2 = 3
input 3 = 4
input 4 = 9
input 5 = a
input 6 = 2.3
输入 1 = 2
输入 2 = 3
输入 3 = 4
输入 4 = 9
输入 5 = 一个
输入 6 = 2.3
If you want a kind of break condition or return within your "loop", then this is a way to do it:
如果您想要一种中断条件或在“循环”中返回,那么这是一种方法:
struct BREAK {};
template <typename ... T>
bool Foo (const T && ... inputs)
{
int i = 0;
auto loop = [&] (auto && input)
{
// Do things in your "loop" lambda
++i;
std::cout << "input " << i << " = " << input << std::endl;
// some conditional breaks / returns
if (input == 9)
throw BREAK();
if (input < 0)
throw false;
if (input == 'a')
throw true;
};
try
{
(loop(inputs), ...);
}
catch (BREAK) {}
catch (bool return_val)
{
return return_val;
}
// more post-loop code
return true;
}
回答by chmeee
You can't iterate, but you can recurse over the list. Check the printf() example on wikipedia: http://en.wikipedia.org/wiki/C++0x#Variadic_templates
你不能迭代,但你可以递归列表。检查维基百科上的 printf() 示例:http: //en.wikipedia.org/wiki/C++0x#Variadic_templates
回答by user3911448
You can use multiple variadic templates, this is a bit messy, but it works and is easy to understand. You simply have a function with the variadic template like so:
您可以使用多个可变参数模板,这有点混乱,但它有效且易于理解。您只需拥有一个带有可变参数模板的函数,如下所示:
template <typename ...ArgsType >
void function(ArgsType... Args){
helperFunction(Args...);
}
And a helper function like so:
还有一个像这样的辅助函数:
void helperFunction() {}
template <typename T, typename ...ArgsType >
void helperFunction(T t, ArgsType... Args) {
//do what you want with t
function(Args...);
}
Now when you call "function" the "helperFunction" will be called and isolate the first passed parameter from the rest, this variable can b used to call another function (or something). Then "function" will be called again and again until there are no more variables left. Note you might have to declare helperClass before "function".
现在,当您调用“function”时,将调用“helperFunction”并将第一个传递的参数与其余参数隔离开来,此变量可用于调用另一个函数(或其他函数)。然后“函数”将被一次又一次地调用,直到没有更多的变量。请注意,您可能必须在“函数”之前声明 helperClass。
The final code will look like this:
最终代码将如下所示:
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...);
}
The code is not tested.
代码没有经过测试。