How to handle parameter packs in the middle of a function signature? - c++

I feel a bit uncomfortable at the moment when using parameter packs. I've got a function
template <class ... Args>
void f( int x, Args ... args, int y )
{}
and of course using it like this works:
f( 5, 3 );
I am wondering why the following call fails:
f( 5, 3.f, 3 );
It seems like a straight-forward use of parameter packs to me, but according to the compiler Args is not expanded.
Of course, I could easily replace f by:
template <class ... Args>
void f( int x, Args ... args )
{
static_assert( sizeof...( args ) >= 1, ... );
extract y from args ...
}
Questions:
Why can't I use parameter packs like this? It seems like the compiler could easily create the replacement code. Or are there any problems with the replacement f() above?
What is the best way to deal with this, if the order of the parameters is really important to me? (Think of std::transform for an arbitrary number of input iterators.)
Why is the usage of parameter packs not forbidden in the situation above? I assume, it is because they could be expanded explicitly, e.g. f<float>( 5, 3.f, 3 )?

Why can't I use parameter packs like this? It seems like the compiler could easily create the replacement code. Or are there any problems with the replacement f() above?
Might be easy in this particular case, might not be in others. The real answer is that the Standard specifies the behavior and the Standard did not specify any transformation... and in general rarely does.
I would note that your transformation does not work with SFINAE, since static_assert are hard-errors, so I am glad the transformation is not performed. (Hint: what if f is overloaded with a single-argument version taking a float, which would get chosen with/without transformation for f(1) ?)
What is the best way to deal with this, if the order of the parameters is really important to me? (Think of std::transform for an arbitrary number of input iterators.)
Explicitly specifying the template pack parameters.
Why is the usage of parameter packs not forbidden in the situation above? I assume, it is because they could be expanded explicitly, e.g. f( 5, 3.f, 3 )?
I think that your assumption is spot on; if explicitly specified this works as expected.

It's a case of you can have other non-variadic template parameters, but only after these non-variadic arguments have been allocated to the non-variadic parameters, can the remainders constitute the parameter pack for the variadic parameter. So you have to move the int y at the end of your argument list to before the variadic parameters. Something like:
template <class... Args>
void f(int x, int y, Args... args)
{}

Related

How to use "type ... pack-name" parameter pack in C++?

The cppreference page on parameter pack states there is a parameter pack like this:
type ... pack-name(optional) (1)
But how do you use it?
This doesn't work and the error is syntactical:
template<int... Ints>
int sum_2_int(Ints... args)
{
return (int)(args + ...);
}
I can't figure out how to use this thing from the description and I don't see an example of the usage anywhere on that page. I may have just skipped it because I am very inexperienced in this part of c++.
EDIT1:
I am not trying to sum an arbitrary amount of integers or whatever types. I've written this function because of my complete lack of understanding of how and where to use this type of parameter pack since I assumed it will be similar to the type (2) typename|class ... pack-name(optional).
EDIT2: Now I know that trying to use Ints... args as a parameter in function definition is futile. I made a new snippet, that works now here. If you know more examples of the usage of this type of parameter pack, please share.
So, what I've learned about type (1) parameter pack:
It is INCORRECT to use as a parameter in the function definition, since template<int... Ints> int foo(...) {...} just means that you should later use your function as int i = foo<1,2,3,4,...>(...);
It can be used as an argument to some function after the expansion happens:
// parameter pack type [2] typename|class ... pack-name(optional)
template <typename... Types>
int sum_to_int(Types... args)
{
return (int)(args + ...); // fold expression used here
}
// parameter pack [1] type ... pack-name(optional)
template <int... Ints>
int sum_ints_statically()
{
return sum_to_int(Ints...);
}
int main()
{
return sum_ints_statically<1,2,3,4,5>(); // will return 15!
}
Thanks Evg and user17732522 for helping me to find the answer.
Please add more usage examples if you know more!
It looks like your function is meant to sum a list of ints. You can provide this list directly with a non-type template parameter pack and no normal parameter or with a type template parameter pack used for the normal parameter types. Not both at the same type. Examples:
template<int... Ints>
int sum_2_int()
{
return (int)(Ints + ...);
}
sum_2_int<1, 2, 3>(); // template arguments must be known at compile time
template<typename... Ints>
int sum_2_int(Ints... args)
{
return (int)(args + ...);
}
sum_2_int(1, 2, 3); // function arguments must be summable
Demo
Note that the second version accepts any type for each of the parameters, but you can constrain them with std::enable_if or C++20 concepts.
To answer your edit: the first version above is exactly how you can use such a parameter pack. Note the expression is (Ints + ...) instead of (args + ...).
You can also have a look at std::integer_sequence, particularly the alias index_sequence and the examples in that page.
I also found this specific application for computing fibonacci numbers at compile-time.

C++ Variadic Templates for a General-Purpose and Fast Data Storage Container Builder

template< typename ... Args>
auto build_array(Args&&... args) -> std::array<typename std::common_
type<Args...>::type, sizeof...(args)>
{
using commonType = typename std::common_type<Args...>::type;
return {std::forward<commonType>(args)...};
}
int main()
{
auto data = build_array(1, 0u, 'a', 3.2f, false);
for(auto i: data)
std::cout << i << " ";
std::cout << std::endl;
}
Hey guys, I cannot understand the above code. So basically, the code is to write a function that takes any number of elements of any type, which can, in turn, be converted into a common type. The function should also return a container having all the elements converted into that common type, and it should also be fast to traverse. This is a books solution.
From what I understand <typename... Args> is to allow a variation of parameters. Then, (Args&&...args) also allows for a variety of parameters, but only rvalues? I do not understand the arrow notation and the rest of the function declaration. Like what is the difference between each of them. Additionally, the book also passes in ? for the templates such as, std::array<?,?>?
Finally, what does the return statement even mean (ending with an ellipsis?) ? and forward?
Sorry, I am rambling on, but I just cannot make sense and obtain a detailed overview of what is going on.
It would be really kind of you if you can elaborate on this?
but only rvalues?
When you see T&&,
if T is not a template parameter, then T&& means an rvalue reference to T, which can bind to rvalules only;
if T is a template parameter, then T&& means a forwarding/universal reference to T, which can bind to both rvalue and lvalues.
Therefore, in your case, since Args is a template parameter (precisely a type template parameter pack, number (2) here), Args&&... args expands to a comma separated sequence of function parameter declarations each of which has type a forwarding reference. For instance, if you pass 3 arguments to build_array, the deduction takes place as if you had a declaration like this:
template<typename Arg1, typname Arg2, typname Arg3>
auto build_array(Arg1&& arg1, Arg2&& arg2, Arg3&& arg3)
-> std::array<typename std::common_type<Arg1, Arg2, Arg3>::type, 3>
what does the return statement even mean (ending with an ellipsis?) ?
Again, ... is to expand some variadic thing in a comma separated sequence of things. So if args in
return {std::forward<commonType>(args)...};
is actually 3 things, then that statement is expanded to
return {std::forward<commonType>(arg1), std::forward<commonType>(arg2), std::forward<commonType>(arg3)};
Notice the position of the ellipsis. f(args)... is expanded to f(arg1), f(arg2), f(arg3), …, whereas f(args...) would be expanded to f(arg1, arg2, arg3, …).
and forward?
That's probably the less easy to understand bit, and would require a dedicated question. However many questions on that topic exist already, so you just have to search for them, rather than asking a new one. Here an answer of mine where I've most clearly explained the difference between std::move and std::forward. If you set understanding that answer of mine as your target, you'll understand std::forward (and std::move) and everything will be clearer.
I do not understand the arrow notation and the rest of the function declaration.
Essentially,
auto f(/* parameters */) -> SomeType
is equivalent to
SomeType f(/* parameters */)
with the advantage that in the former SomeType can refer to types that are in /* parameters */, if needed. In your case the return type makes use of Args.
the book also passes in ? for the templates such as, std::array<?,?>?
Probably the book is just trying to guide you through argument deduction, and it's using ? to mean "we don't know yet what it is; keep reading".

How exactly is expansion of a parameter pack evaluated with std::forward?

I wanted to better understand parameter pack expansions, so I decided to research a bit and, what once seemed obvious to me, stopped being so obvious after trying to understand what exactly is going on. Let's examine a standard parameter pack expansion with std::forward:
template <typename... Ts>
void foo(Ts&& ... ts) {
std::make_tuple(std::forward<Ts>(ts)...);
}
My understanding here is that for any parameter pack Ts, std::forward<Ts>(ts)... will result in a comma-separated list of forwarded arguments with their corresponding type, e.g., for ts equal 1, 1.0, '1', the function body will be expanded to:
std::make_tuple(std::forward<int&&>(1), std::forward<double&&>(1.0), std::forward<char&&>('1'));
And that makes sense to me. Parameter pack expansion, used with a function call, results in a comma-separated list of calls to that function with appropriate arguments.
What seems to be bothering me is why then would we sometimes need to introduce the comma operator (operator,), if we want to call a bunch of functions in a similar manner? Seeing this answer, we can read this code:
template<typename T>
static void bar(T t) {}
template<typename... Args>
static void foo2(Args... args) {
(bar(args), ...); // <- notice: comma here
}
int main() {
foo2(1, 2, 3, "3");
return 0;
}
followed by information that it will result in the following expansion:
(bar(1), bar(2), bar(3), bar("3"));
Fair, makes sense, but... why? Why doing this, instead:
template<typename... Args>
static void foo2(Args... args) {
(bar(args)...); // <- notice: no comma here
}
doesn't work? According to my logic ("Parameter pack expansion, used with a function call, results in a comma-separated list of calls to that function with appropriate arguments"), it should expand into:
(bar(1), bar(2), bar(3), bar("3"));
Is it because of bar() returning void? Well, changing bar() to:
template<typename T>
static int bar(T t) { return 1; }
changes nothing. I would imagine that it would just expand to a comma-separated list of 1s (possibly doing some side effects, if bar() was designed as such). Why does this behave differently? Where is my logic flawed?
My understanding here is that for any parameter pack Ts, std::forward<Ts>(ts)... will result in a comma-separated list of forwarded arguments with their corresponding type
Well, there's your problem: that's not how it works. Or at last, not quite.
Parameter pack expansions and their nature are determined by where they are used. Pack expansions, pre-C++17, can only be used within certain grammatical constructs, like a braced-init-list or a function call expression. Outside of such constructs (the previous list is not comprehensive), their use simply is not allowed. Post-C++17, fold expressions allow them to be used across specific operators.
The reason for this is in part grammatical. Consider this: bar(1, (2, 3), 5). This calls the function with 3 arguments; the expression (2, 3) resolves down to a single argument. That is, there is a difference between the comma used in an expression and the comma used as a separator between values to be used in a function call. This difference is made at the grammatical level; if I want to invoke the comma operator in the middle of a sequence of function arguments, I have to put that whole thing in () so that the compiler will recognize the comma as a comma expression operator, not a comma separator.
Non-fold pack expansions effectively expand to use the separation comma, not the expression comma. As such, they can only be expanded in places where the separation kind of comma is valid.
The reason (bar(args)...) doesn't work is because a () expression cannot take the second kind of comma.

What is the difference between std::invoke and std::apply?

They are both used as a generic method of calling functions, member functions and generally anything that is callable. From cppreference the only real difference I see is that in std::invoke the function parameters (however many they are) are forwarded to the function, whereas in std::apply the parameters are passed as a tuple. Is this really the only difference? Why would they create a separate function just to handle tuples?
Is this really the only difference? Why would they create a separate function just to handle tuples?
Because you really need both options, since they do different things. Consider:
int f(int, int);
int g(tuple<int, int>);
tuple<int, int> tup(1, 2);
invoke(f, 1, 2); // calls f(1, 2)
invoke(g, tup); // calls g(tup)
apply(f, tup); // also calls f(1, 2)
Consider especially the difference between invoke(g, tup), which does not unpack the tuple, and apply(f, tup), which does. You sometimes need both, that needs to be expressed somehow.
You're right that generally these are very closely related operations. Indeed, Matt Calabrese is writing a library named Argot that combines both operations and you differentiate them not by the function you call but rather by how you decorate the arguments:
call(f, 1, 2); // f(1,2)
call(g, tup); // g(tup)
call(f, unpack(tup)); // f(1, 2), similar to python's f(*tup)
You use std::apply because:
1: Implementing apply, even if you have access to std::invoke is a big pain. Turning a tuple into a parameter pack is not a trivial operation. apply's implementation would look something like this (from cppref):
namespace detail {
template <class F, class Tuple, std::size_t... I>
constexpr decltype(auto) apply_impl(F&& f, Tuple&& t, std::index_sequence<I...>)
{
return std::invoke(std::forward<F>(f), std::get<I>(std::forward<Tuple>(t))...);
}
} // namespace detail
template <class F, class Tuple>
constexpr decltype(auto) apply(F&& f, Tuple&& t)
{
return detail::apply_impl(
std::forward<F>(f), std::forward<Tuple>(t),
std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Tuple>>>{});
}
Sure, it's not the hardest code in the world to write, but it's not exactly trivial either. Especially if you don't the index_sequence metaprogramming trick.
2: Because calling a function by unpacking the elements of a tuple is rather useful. The basic operation it exists to support is the ability to package up a set of arguments, pass that set around, and then call a function with those parameters. We already technically have the ability to do that with a single parameter (by passing the value around), but through apply, you gain the ability to do it with multiple parameters.
It also allows you to do metaprogramming tricks, like meta-programmatically doing marshalling between languages. You register a function with such a system, which is given the function's signature (and the function itself). That signature is used to marshal the data through metaprogramming.
When the other language calls your function, the metaprogram-generated function walks the list of parameter types and extracts value(s) from the other language based on those types. What does it extract them into? Some kind of data structure that holds the values. And since metaprogramming cannot (easily) build a struct/class, you instead build a tuple (indeed, supporting metaprogramming like this is 80% of why tuple exists).
Once the tuple<Params> is built, you use std::apply to call the function. You can't really do that with invoke.
3: You don't want to make everyone stick parameters into a tuple just to be able to perform the equivalent of invoke.
4: You need to establish the difference between invokeing a function that takes a tuple, and applying to unpack the tuple. After all, if you're writing a template function that performs invoke on parameters the user specifies, it'd be terrible if the user just so happened to provide a single tuple as an argument and your invoke function unpacked it.
You could use other means to differentiate the cases, but having different functions is an adequate solution for the simple case. If you were writing a more generalized apply-style function, where you want to be able to unpack tuples in addition to passing other arguments or unpack multiple tuples into the argument lists (or a combination of these), you would want to have a special super_invoke that could handle that.
But invoke is a simple function for simple needs. The same goes for apply.

variadic template arguments unpacking

For each argument I need apply two nested function:
obj.apply(someFilter(arg)); // arg is one argument, but here
// should be an unpacking of args
I don't know how to write unpacking for such case.
I saw this:
pass{([&]{ std::cout << args << std::endl; }(), 1)...};
on wiki, but again don't know how to apply this for my case.
It's actually quite simple:
You can put arbitrary expression inside the unpack of an variadic templates argument pack:
obj.apply(someFilter(arg))...
This will give you the result of obj.apply as a coma seperated list. You can then pass it to a dummy function:
template<typename... Args> swallow (Args&&...) {}
swallow(obj.apply(someFilter(arg))...);
To swallow the comma seperated list.
Of course, this assumes that obj.apply returns some kind of object. If not you can use
swallow((obj.apply(someFilter(arg)), 0)...);
to make actual (non void) arguments
If you don't know what obj.apply` returns (result might have overloaded the comma operator), you can disable the use of custom comma operators by using
swallow((obj.apply(someFilter(arg)), void(), 0)...);
Should you actually need to evaluate the items in order (which doesn't seem very likely from the question), you can abuse array initialization syntax instead of using a function call:
using Alias=char[];
Alias{ (apply(someFilter(args)), void(), '\0')... };
Here is a robust way to do an arbitrary set of actions on a parameter pack. It follows the principle of least surprise, and does the operations in order:
template<typename Lambda, typename Lambdas>
void do_in_order( Lambda&& lambda, Lambdas&& lambdas )
{
std::forward<Lambda>(lambda)();
do_in_order( std::forward<Lambdas>(lambdas)... );
}
void do_in_order() {}
template<typename Args>
void test( Args&& args ) {
do_in_order( [&](){obj.apply(someFilter(std::forward<Args>(args)));}... );
}
Basically, you send a pile of lambdas at do_in_order, which evaluates them front to back.
I assume that the code has multiple args as a parameter pack? Try:
obj.apply( someFilter( arg )... );
as parameter unpacking applies to the expression, so each element of the parameter pack get's expanded into someFilter( arg ).