Parameter pack to parameter pack mapping - c++

I want to remap a parameter pack to a different type parameter pack.
More precisely I have a function:
template<typename ...type_pack, typename Function = void(type_pack&&...)>
constexpr decltype(auto) zip(Function&& op, type_pack&&... pack)
{
static_for<0, N_lhs>([&](auto i)
{
op(pack[i]...);
});
return;
}
Basically I want to create a parameter pack of the results from applying the [] to the pack elements. Note that i here is an integral constant, and the static_for is compile time, you can assume that [] is constexpr. I do not have much control over op, so it expects a parameter pack and not a tuple.
Edit:
Seems like I was under the misunderstanding that op(pack[i]...) was causing the issue, when in fact this is a legal C++ construct (I thought it was illegal). So it seems like something was actually wrong with my static_for loop. My question was originally regarding op(pack[i]...) so I will keep it as is.
I prepared a more general example not using [] but a different arbitrary function just for a sanity check: https://godbolt.org/z/h8Hbbt
Is there a place in the standard where this pack expansion behaviour is mentioned - namely the fact that functions may be applied on top?

Basically I want to create a parameter pack of the results from applying the [] to the pack elements.
Do you mean something as follows?
template <typename ... type_pack,
typename Function = void(decltype(std::declval<type_pack>()[0])...)>
constexpr decltype(auto) zip(Function&& op, type_pack&&... pack)
{
/* ... */
}
Please, prepare a minimal but complete example (static_for, mainly) if you want a more tested answer.

A parameter pack can be expanded in terms of a pattern.
template <typename... T>
void test(T... t) {
(std::cout << ... << static_cast<int>(t));
}
Here it is exanded as a folding expression, but it works in the same way in a regular pack expansion. The pattern here is static_cast<int>(t) and it will expand to
std::cout << static_cast<int>(t1) << static_cast<int>(t2) << ... << static_cast<int>(tN);

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

Why can the type constraint `std::convertible_to` be used with only one template argument?

I've scrolled and searched through the standard and cppreference for hours to no avail, would really appreciate if someone could explain this occurance for me:
I am looking at the standard concept std::convertibe_to. Here's a simple example that I do understand
class A {};
class B : public A {};
std::convertible_to<A, B>; // false
std::convertible_to<B, A>; // true
Works as expected.
Now there is also another possible way to use it, that I don't quite understand
void foo(std::convertible_to<A> auto x) { /* ... */ }
, and this function can easily accept any type convertible to A. This is weird though, because the first template parameter ("From") is essencially dropped, and deduced on function call. This following function would also work, and I'm fairly certain it's actually equivalent to the previous one
template<typename T, std::convertible_to<T> S>
void foo(S x) { /* ... */ }
again the type of x is deduced when we call foo.
This works, despite the template requiring two parameters. I tried also with std::derived_from and it seems to work. This form of specifying a concept with only one template parameter even appears in the standard itself, so there must be some piece of syntax that explains it.
Notice that the only version of std::convertible_to that exists is in fact one that takes two template parameters.
Could anyone clarify why this works?
void foo( constraint<P0, P1, P2> auto x );
this translates roughly to
template<contraint<P0, P1, P2> X>
void foo( X x );
which translates roughly to
template<class X> requires constraint<X, P0, P1, P2>
void foo( X x );
notice how the type X is prepended to the template arguments of the constraint.
So in your case,
template<typename T, std::convertible_to<T> S>
void foo(S x) { /* ... */ }
is roughly
template<typename T, class S>
requires std::convertible_to<S, T>
void foo(S x) { /* ... */ }
(I say roughly, because I believe they are not exactly equivalent in subtle ways. For example, the second one introduces the name X, while the first does not. And there are probably other differences of similar scale; what I mean is that understanding the translation will give you an understanding of what is translated. This is unlike for(:) loop-for(;;) loop correspondence; the standard specifies for(:) loops in terms of for(;;) loops, which isn't what I'm claiming above.)
There are several locations where a concept name can be used where the first argument to the template concept is not supplied in the template argument list. Constraining an auto deduced variable is one of them.
The first argument in these cases is provided by some expression, typically using template argument deduction rules. In the case of a constrained function parameter, the first argument is determined by the template function itself. That is, if you call foo(10), template argument deduction will deduce the auto template parameter as an int. Therefore, the full concept will be convertible_to<int, A>.

when to use template non-type classes or plain arguments in constexpr functions

I am very unclear when to use non-type template arguments (C++20) or normal arguments in constexpr functions. It's unclear to me, what the restrictions are and when to switch from pure parameters to non-type template parameters (see Live).
Here an example which illustrates the clunkiness with normal arguments:
template<typename Tuple, typename Pred>
constexpr auto getLambda(Tuple&& tuple, Pred&& pred)
{
return [=](auto I, auto J) {
return pred(std::get<I>(tuple), std::get<J>(tuple));
};
}
template<typename T>
struct A
{
constexpr A(T t) : val(t){};
T val;
};
int main()
{
static constexpr auto t = std::make_tuple(A{10.0}, A{1});
constexpr auto p = [](auto&& a, auto&& b) { return a.val < b.val; };
constexpr auto s = getLambda(t, p);
//constexpr auto b = s(1,0); // that does unfortunately not compile -> go back write differently... :-|||
}
Mostly I first try to use normal arguments like above, and after cumbersome fiddling with compile errors about non-constant expressions, try an approach with template<auto t> (non-type template paramters. Mostly having then two implementations one for each use-case (this seems stupid to me...)
Its sounds to me that modern generic programming with C++20 tends towards compile-time computations using constexpr together with some type-meta-programming.
Could anyone shed some light into this rather new "dark-corner" of C++. I probably misunderstand
when something is not a constant-expression and when it is...
The short version: Use non-type template parameter to set non-type template arguments (more general everywhere, where you need a constant expression) and normal arguments for everything else.
The thing about constexpr functions you always have to keep in mind is that they can also be called at runtime. So every normal argument is not necessarily a constant expression. Hence you cannot use it to provide a non-type template argument (as the I in std::get<I>).
Of course one could argue that when called to calculate a constexpr variable the passed arguments are always constant expressions and could be used as such also inside the function. But it would be unexpected if a constexpr function works at compile time but not anymore at runtime.
One could expect that with the new consteval keyword in C++20, one could use normal arguments to consteval functions in constant expressions, since we know that these arguments have to be constant expressions. But this does not seem to be the case: https://godbolt.org/z/guz7FQ Why this is the case I do not know. But in general I like the seperation between normal variables and non-type template arguments.

How to call the idiom of using an array to apply a function to a variadic pack

Here is the idiom in question:
template<typename... T>
void f(T... t) {
int temp[] = {(g(t), 0)...};
}
This will be compiled as g(t0); g(t1); ..., order of function calls is guaranteed by C++11[dcl.init.list]/4.
A better version uses std::initializer_list instead of array, but it's not important here.
The question is: how should we call this idiom?
Upd:
Basically, it's the idiom which we should advise to people to use it instead of recursion, i.e. to replace two overloads
void f() {}
void f(H head, T... tail) { g(head); f(tail...); }
with single
void f(T... t) { int temp[]{(g(t), 0)...}; }
Of course we can call it "An idiom which will be replaced by the Fold Expressions" but I hope there is a proper term for it.
Pack expansion.
C++11 §5.1.2/23 in [expr.prim.lambda]:
” A capture followed by an ellipsis is a pack expansion (14.5.3). [Example:
template<class... Args>
void f(Args... args) {
auto lm = [&, args...] { return g(args...); };
lm();
}
—end example ]
I think that covers it. The pack expansion without applying a function can be viewed as one applying an identity function.
C++11 §14.5.3/4 in [temp.variadic]:
A pack expansion consists of a pattern and an ellipsis, the instantiation of which produces zero or more
instantiations of the pattern in a list (described below). […]
Perhaps "function call expanded across parameter pack"?
Let's distinguish folding and mapping.
fold turns series of input data to a single value.
map turns series of inputs to series of outputs.
(Of course, mapping may be expressed as folding series of scalars to a single list).
Here we map all inputs to a nullary value.
If we highlight side effects of g(x0), g(x1), etc - we can talk about mapping.
If we highlight producing void, - we can talk about pure folding. But this is meaningless, I think.
std::transform is mapping. std::accumulate is folding. What is std::for_each? Mapping or folding? (BTW, it is folding, because it returns a stateful function object with accumulated state).
As far as the input function is unary, not binary, it is mapping (fused with folding).
So. I'd like to give this idiom "variadic for-each".