#So basically, what is being done here?

1 messages · Page 1 of 1 (latest)

copper coral
#

I had a look at the cppreference page about parameter packs and I found something called variadic class templates. And tbh, I don't quite get how this relates to parameter packs. I assume it's a usecase for parameter packs? Are there any usecases for template classes like this one here for example?

tribal lionBOT
#

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

Screenshots!

Your message appears to contain screenshots but no code. Please send code and error messages in text instead of screenshots if applicable!

copper coral
#

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

signal cliff
left knot
#

the use case is just what it says: being able to accept any number of arguments

silent basalt
#

Parameter packs are groups of values of different types, pretty much manifestations of a variadic class template

copper coral
signal cliff
#

well, perhaps a super-simple example: the struct could inherit from all of the types you passed to it

left knot
#

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

copper coral
#

So maybe not something for me to dig into now immediately?

left knot
#
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.

signal cliff
copper coral
left knot
#

nope they mean the same thing 😄

signal cliff
#

in recent language versions they're the same ya

left knot
#

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

left knot
#

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

copper coral
#

or mean the same thiung

#

What is a non class-type?

left knot
#

I mean that using class sounds like it implies something, but it doesn't

left knot
copper coral
#

So that's the difference. One of the types can't be primitive types with class ... Types?

left knot
#

no there is no difference

copper coral
#

Okay so they just have different implications, there is no underlying functional difference.

left knot
#

template<typename T> and template<class T> both do exactly the same thing:
declare a type parameter that will accept any type

copper coral
#

It just looks different, is what you are saying

left knot
#

right

copper coral
#

How do I know if I can hold off with the hold expressions for my usecases?

left knot
#

put it like this:
using typename T to say "I want any type" feels more right to me than writing class T

copper coral
#

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

left knot
#

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

copper coral
#

why are binary operators used with that? Two seemingly unrelated concepts from my perspective.

left knot
#

it's there for binary operators

#

you have some parameter pack of values

#

and you would like to add them all together

copper coral
#

Ah. FOR them.

left knot
#

without fold expressions, you'd have to write some kind of recursvie function to do it

copper coral
#

So for example

a OR additional argument OR additional argument OR additional ar...

left knot
#

possible, but a bit annoying for simple stuff

copper coral
#

When does it unpack them? Compile time?

left knot
#

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

copper coral
#

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?

left knot
#

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

copper coral
#

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?

left knot
#

hm, how do you mean

copper coral
#

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.

left knot
#

yeah

copper coral
#

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>?

left knot
#

C-style variadic parameters don't work well for C++ class types

#

and you need some other way to know what type they are

copper coral
#

This is how I was introduced to ellipsis and I felt like it did not project well onto other topics like parameter packs

left knot
#

for printf, you can look at the format string to see what the type of the arguments is supposed to be

copper coral
left knot
#

"%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

signal cliff
left knot
#

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

copper coral
left knot
#

maybe? 😄

signal cliff
#

there's parameter packs, pack expansion, and folds; I'd try not to refer to them all as folds 🙂

left knot
#
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

copper coral
left knot
#

(0 + ... + args) is the fold expression

signal cliff
#

(0 + ... + args) is a fold expression template<typename... Args> declares a parameter pack

copper coral
left knot
#
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

copper coral
#

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;    
}
left knot
#

correct, fold expressions involve binary operators

#

this is just a pack expansion

copper coral
#

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.

left knot
#

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

copper coral
#

if putting ... after the thing you want to expand, why do the ... come before args in a fold expression?

left knot
#

in fold expressions, you an write them two ways

copper coral
#

Whilst in this case, you can't?

left knot
#

in a fold expression, which side ... and the pack are on determines which way the parantheses nest

#

i.e. the associativity

copper coral
#

What do you call the case when you can't write them both ways? A non-fold expression?

left knot
#

a pack expansion

copper coral
#

Which I somehow need std::forward to do?

signal cliff
#

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

copper coral
#

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
*/
signal cliff
#

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)

left knot
#

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 😄

copper coral