#So basically, what is being done here?
1 messages · Page 1 of 1 (latest)
When your question is answered use !solved to mark the question as resolved.
Remember to ask specific questions, provide necessary details, and reduce your question to its simplest form. For tips on how to ask a good question use !howto ask.
@copper coral
Your message appears to contain screenshots but no code. Please send code and error messages in text instead of screenshots if applicable!
Did I do something wrong? There isn't really any code to show, I don't get it.
I know the code is in a screenshot, but that's because it's from a specific place with an explanation attached to it
if applicable, it's not, although maybe a link would be wise https://en.cppreference.com/w/cpp/language/parameter_pack
the use case is just what it says: being able to accept any number of arguments
Parameter packs are groups of values of different types, pretty much manifestations of a variadic class template
The part that throws me off with the example is probably that the struct is empty. It doesn't demonstrate how a struct could do anythingwith those any number of parameters.
well, perhaps a super-simple example: the struct could inherit from all of the types you passed to it
std::tuple is a real-world example
it stores an instance of each of the type arguments as members
but the implementation of that is a bit tricky so not great slide code 😄
it's not yet possible to do something like
template<typename... Types>
struct tuple {
Types... members;
};
but you can use ... to expand the pack in all kinds of expressions
So maybe not something for me to dig into now immediately?
template<typename... Args>
void foo(const Args&... args) {
std::vector<std::string> strings{std::to_string(args)...};
// do something with strings, I guess
}
there are "fold-expressions" that let you fold an operator over a pack,
like (args + ... + 0) to sum them all up, or (args && ...) etc.
most of the real uses I can think of are at the end of a mess of metaprogramming, but here's that fake use example anyways: https://godbolt.org/z/ox4e6183x
One thing I see in the examples is
typename ... Args
However I have also witnessed
class ... Args
There are a lot of c++ concepts involved in this syntax. The ellipsis I think it's called, as well as the keywords typename and class. That's a lot to take in. class ... declares any number of classes, and typename declares any number of types?
nope they mean the same thing 😄
in recent language versions they're the same ya
class does not place any constraints on the type parameters
some people still prefer to use class because it's shorter
I prefer typename because it doesn't have that false implication that the types must be class types
it comes in handy when you have to deal with arbitrary numbers of types or values of different types...
so usually in heavily generic code
for example, std::vector<T> has an emplace_back function that will accept any number of arguments, and construct a new T in the vector, passing all those arguments into it
variadic templates make that possible
You just said they were the same thing
or mean the same thiung
What is a non class-type?
I mean that using class sounds like it implies something, but it doesn't
primitive types like ints, floats, bools, pointers, enums, references, ...
So that's the difference. One of the types can't be primitive types with class ... Types?
no there is no difference
Okay so they just have different implications, there is no underlying functional difference.
template<typename T> and template<class T> both do exactly the same thing:
declare a type parameter that will accept any type
It just looks different, is what you are saying
right
How do I know if I can hold off with the hold expressions for my usecases?
put it like this:
using typename T to say "I want any type" feels more right to me than writing class T
I find the fold expressions confusing
I know thye do something recursive and they require some parantheses to unpack them apparently
unpack, not sure if that's the right term
you can do normal unpacking/expanding with expression...
and it will expand to a comma separaed list
like an argument list for a function
fold expressions combine all the values with some binary operator
why are binary operators used with that? Two seemingly unrelated concepts from my perspective.
it's there for binary operators
you have some parameter pack of values
and you would like to add them all together
Ah. FOR them.
without fold expressions, you'd have to write some kind of recursvie function to do it
So for example
a OR additional argument OR additional argument OR additional ar...
possible, but a bit annoying for simple stuff
right
When does it unpack them? Compile time?
yeah
also, the expression you expand can be more complicated
it can do more than just do arg + arg + arg + arg .... + 0
if you write (whatever(args) + ... + 0) it will expand to whatever(arg0) + whatever(arg1) + ... + 0
etc.
(same for foo(whatever(args)...) -> foo(whatever(arg0), whatever(arg1), ...)
What if the arugments are read from a file? Like a json array? Will the answer to that question be valid for both fold expressions and parameter packs?
doesn't really apply
you can only have a parameter pack in a template
and where ever you call that template, you have already decided at compile time how many args you're giving it
when you have variable number of values at runtime, like in a container or something,
you can use regular run-time functions to add them all up, like std::accumulate
Okay so let's say that I have a json file... I sorta need to define what I expect to get from it, hence the compiler still gets to know what functions it needs to generate from the template?
hm, how do you mean
A very very shabby example which doesn't quite reflect on the situation completely but for each object I sorta do expect a certain number of parameters, eventho the json array does contain an X number of items
So here a function is explicitly called with a certain number of arguments, I am thinking.
yeah
Alright, I guess that makes a bit more sense out of it.
Just one more question though. What happened with this, why do I not see this syntax that much? Why use parameter packs and not <cstdarg>?
C-style variadic parameters don't work well for C++ class types
and you need some other way to know what type they are
This is how I was introduced to ellipsis and I felt like it did not project well onto other topics like parameter packs
for printf, you can look at the format string to see what the type of the arguments is supposed to be
So I felt like the resources ppl say are good just sorta left me hanging when I tried going beyond what I've already read
"%s %s %s" tells printf to expect 3 arguments, which are strings,
so it can run the right code to extract 3 such arguments from va_list
ime there's not much good out there on folds, but https://www.foonathan.net/2020/05/fold-tricks/ at least helped me on my way
but all that code for all possible types has to be hard-coded into printf
(or I guess it can be extended with some API but..)
with variadic template, you don't need to do any of that -- you get to know the types directly
Are parameter packs and/or fold expressions referred to as folds?
maybe? 😄
there's parameter packs, pack expansion, and folds; I'd try not to refer to them all as folds 🙂
template<typename... Args>
auto sum(Args... args) {
return (0 + ... + args);
}
say you have this function that adds all the arguments together
if you call sum(1, 2.5, 3.f), the compiler will create a function like
auto sum<int, double, float>(int arg0, double arg1, float arg2) {
return (((0 + arg0) + arg1) + arg2);
}
you don't have to know up-front all the possible types that can be passed in, and figure out a way to identify them, and write code for handling all their combinations.
as long as they all behave similarly enough and you can add the values together, it just works
So this is not a fold expression, but a parameter pack?
(0 + ... + args) is the fold expression
(0 + ... + args) is a fold expression template<typename... Args> declares a parameter pack
Maybe I sound like a goldfish for saying this, but I haven't quite managed to pick up on the difference.
int foo(int arg) {
return arg + 5;
}
int arg declares a parameter
arg + 5 is an expression
Args... args declares a parameter pack (named args)
a fold expression is just a particular kind of expression, which you can use on parameter packs
I see. So the thing in the template thingy is the parameter pack, and the std::forward<Args>(args) here is... not a fold expression? This looks quite different.
template <typename T, typename ... Args>
static Instantiate(Args&& ... args){
if(!pge.has_updatable){pge.add_updatable(this);}
T instance = T(std::forward<Args>(args)...);
instance.OnCreate();
return instance;
}
I know that this piece of code is supposed to unpack args somehow, but not sure how that works
std::forward<Args>(args)...
in this case.
std::forward is just a function (template)
the expansion happens because the parameter pack args is involved, and you used ... after the expression you want to expand
you can expand any sort of expression that uses a parameter pack in it somewhere
foo(whatever(args)...) -> foo(whatever(arg0), whatever(arg1), ...)
if putting ... after the thing you want to expand, why do the ... come before args in a fold expression?
in fold expressions, you an write them two ways
Whilst in this case, you can't?
in a fold expression, which side ... and the pack are on determines which way the parantheses nest
i.e. the associativity
What do you call the case when you can't write them both ways? A non-fold expression?
a pack expansion
Which I somehow need std::forward to do?
nah, perfectly fine to use pack expansion without std::forward, std::forward usually gets into another topic called perfect forwarding which is often great for these wrapper functions
Huh.
Alright, so I wrote a bit of a summary. Does this seem right? Hope it's readable in whatever view you have discord in.
/*
Basically with fold expressions you can unpack expressions both by doing unary right fold (E op ...) as well as unary left fold (... op E). Then there is binary right fold and binary left fold.
See https://en.cppreference.com/w/cpp/language/fold
*/
template<typename... Args>
auto sum(Args... args) {
return (0 + ... + args);
}
//this is expanded to the following code by the compiler
auto sum<int, double, float>(int arg0, double arg1, float arg2) {
return (((0 + arg0) + arg1) + arg2);
}
/*
If you use parameter packs and expand the argument by doing args... is referred to as pack expansion. Here is an example:
*/
template <typename T, typename ... Args>
static Instantiate(Args&& ... args){
if(!pge.has_updatable){pge.add_updatable(this);}
T instance = T(std::forward<Args>(args)...);
instance.OnCreate();
return instance;
}
/*
Credits to Melak47 for some of the examples provided here
*/
for instance, in the above code you coulda gone with
T instance = T(args...);
which is probably fine for some T's
what std::forward does here (together with using a && pack and some other rules https://en.cppreference.com/w/cpp/language/reference#Forwarding_references) is ensure that the cvref qualifications a given arg came into "Instantiate" isn't changed by passing it to the constructor of T (so anything passed by reference to Instantiate stays a reference when passed into T's constructor, anything std::move'd when passed into Instantiate stays a && when you pass it into T's constructor)
btw,
template <typename... Args>
void foo(Args... args) {}
there are actually two packs here
typename... Args declares a template parameter pack. a pack of type-template-parameters in this case
and Args... args a parameter pack, a pack function parameters
both of those can be expanded.
Args... would expand to a list of the types,
which you could pass as template arguments to something, like tuple
std::tuple<Args...>
(which will expand to e.g. std::tuple<int, bool>)
and you can also expand multiple packs at the same time
which is what std::forward<Args>(args)... does
(both packs have to be the same size for that to work)
but don't worry about it too much -- you don't have to memorize this now 😄
looks right to me
Okay I added this
/*
Additionally in BOTH cases, there are actually two parameter packs. ... Args and ... args.
typename... Args declares a template parameter pack. a pack of type-template-parameters in this case
and Args... args a parameter pack, a pack function parameters
*/