如何迭代打包的可变参数模板参数列表?
我正在尝试找到一种方法来迭代包可变参数模板参数列表。 现在,与所有迭代一样,您需要某种方法来了解打包列表中有多少参数,更重要的是如何从打包参数列表中单独获取数据。
总体思路是迭代列表,将所有 int 类型的数据存储到一个向量中,将所有 char* 类型的数据存储到一个向量中,并将所有 float 类型的数据存储到一个向量中。在此过程中,还需要一个单独的向量来存储参数的顺序的各个字符。例如,当您使用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 数据的问题,并使其更加类型安全和可用。
感谢您的理解,我期待为您提供帮助。
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.
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:
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);
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.
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.
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.
Thanks for your undertstanding, and I'm looking forward to help.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(10)
如果您的输入都属于同一类型,请参阅
OMGtechy
的精彩答案。对于混合类型,我们可以使用 折叠表达式 (在
c+ 中引入+17
)与可调用(在本例中为 lambda ):<一href="https://godbolt.org/z/Erv61TTbb" rel="noreferrer">现场演示
(感谢 glades 在评论中指出我不需要显式地将
输入
传递给 lambda,这使得它变得更加简洁。)如果您需要的话 。
return
/break
在循环中,这里有一些解决方法:抛出
可能会导致此函数的速度大幅减慢;因此,只有在速度不重要或者break
/return
确实异常时才使用此选项。后面这些答案确实是一种代码味道,但表明它是通用的。
If your inputs are all of the same type, see
OMGtechy
's great answer.For mixed-types we can use fold expressions (introduced in
c++17
) with a callable (in this case, a lambda):Live demo
(Thanks to glades for pointing out in the comments that I didn't need to explicitly pass
inputs
to the lambda. This made it a lot neater.)If you need
return
/break
s in your loop, here are some workarounds:throw
s can cause tremendous slow down of this function; so only use this option if speed isn't important, or thebreak
/return
s are genuinely exceptional.These latter answers are honestly a code smell, but shows it's general-purpose.
基于范围的 for 循环非常棒:
对我来说,这会产生输出:
这是一个示例没有
std::any
,对于那些不熟悉std::type_info
的人来说可能更容易理解:正如您所期望的,这会产生:
Range based for loops are wonderful:
For me, this produces the output:
Here's an example without
std::any
, which might be easier to understand for those not familiar withstd::type_info
:As you might expect, this produces:
如果您想将参数包装到
any
,您可以使用以下设置。我还使any
类变得更加有用,尽管它在技术上并不是any
类。然而,可以编写函数来访问可变参数模板函数中的第 n 个参数,并将函数应用于每个参数,这可能是实现您想要实现的任何目标的更好方法。
If you want to wrap arguments to
any
, you can use the following setup. I also made theany
class a bit more usable, although it isn't technically anany
class.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.
您可以通过使用 {} 之间的参数包对其进行初始化来创建它的容器。只要 params... 的类型是同质的或者至少可以转换为容器的元素类型,它就可以工作。 (使用 g++ 4.6.1 测试)
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)
目前没有特定的功能,但您可以使用一些解决方法。
使用初始化列表
一种解决方法使用以下事实:初始化列表 的子表达式在命令。
int a[] = {get1(), get2()}
将在执行get2
之前执行get1
。也许折叠表达式在未来的类似技术中会派上用场。要对每个参数调用do()
,您可以执行以下操作:但是,这仅在
do()
返回int
时有效>。您可以使用逗号运算符来支持不返回的操作一个合适的值。要执行更复杂的操作,您可以将它们放入另一个函数中:
请注意,使用通用 lambdas (C++14),您可以定义一个函数来为您执行此样板文件。
使用递归
另一种可能性是使用递归。这是一个小示例,定义了与上面类似的函数
do_for
。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 lists are evaluated in order.
int a[] = {get1(), get2()}
will executeget1
before executingget2
. Maybe fold expressions will come handy for similar techniques in the future. To calldo()
on every argument, you can do something like this:However, this will only work when
do()
is returning anint
. You can use the comma operator to support operations which do not return a proper value.To do more complex things, you can put them in another function:
Note that with generic lambdas (C++14), you can define a function to do this boilerplate for you.
Using recursion
Another possibility is to use recursion. Here is a small example that defines a similar function
do_for
as above.这不是人们通常使用 Variadic 模板的方式,根本不是。
根据语言规则,不可能对可变参数包进行迭代,因此您需要转向递归。
示例(实际操作),假设我们有
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),因为没有参数,从而结束递归因此:
Foo
),则无法选择重载,从而导致编译时错误。需要注意的是:自动转换意味着
double
将选择重载 (b),而short
将选择重载 (a)。如果这不是所希望的,那么需要引入 SFINAE,这使得该方法稍微复杂一些(至少是它们的签名),例如:其中
is_int
会是这样的:不过,另一种选择是考虑变体类型。例如:
它已经存在,具有所有实用程序,它可以存储在矢量中,复制等......并且看起来非常像您需要的,即使它不使用可变模板。
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.
Example (in action), suppose we have
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 recursionThus:
std::string const&
)Foo
), then no overload can be selected, resulting in a compile-time error.One caveat: Automatic conversion means a
double
would select overload (b) and ashort
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:Where
is_int
would be something like:Another alternative, though, would be to consider a variant type. For example:
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.您无法迭代,但可以递归列表。检查维基百科上的 printf() 示例: http://en.wikipedia.org/wiki /C++0x#Variadic_templates
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
您可以使用多个可变参数模板,这有点混乱,但它有效并且易于理解。
您只需拥有一个带有可变参数模板的函数,如下所示:
和一个辅助函数,如下所示:
现在,当您调用“function”时,将调用“helperFunction”并将第一个传递的参数与其余参数隔离,该变量可用于调用另一个函数(或其他东西)。然后“函数”将被一次又一次地调用,直到没有更多的变量为止。请注意,您可能必须在“function”之前声明 helperClass。
最终代码将如下所示:
该代码未经测试。
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:
And a helper function like so:
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".
The final code will look like this:
The code is not tested.
输出:
Output:
我用它来让它与多种不同的类类型一起工作。这样你就可以拥有一个抽象类,我将接受它的任何子类
I used this to get it working with multiple different class types. this way you can have an abstract class and I will accept any of its subclasses