Variadic templates. Tuples, unpacking and more
In this post I will talk about templates with a variable number of parameters. As an example, the simplest implementation of the tuple class will be given . I will also talk about unpacking tuple and substituting the values stored there as arguments to the function. And finally, I will give an example of using the above techniques to implement deferred function execution, which can be used, for example, as an analogue of finally blocks in other languages.
A template with a variable number of parameters ( variadic template ) is a template of a function or class that accepts the so-called parameter pack . When declaring a template, it looks like this
Such a record means that the template can accept 0 or more types as its arguments. In the body of the template, the usage syntax is slightly different.
A call to foo (1,2.3, “abcd”) is instantiated in foo(1, 2.3, “abcd”) . In parameter pack 's has many interesting properties (eg they can be used in sheet capturing lambdas or brace-the init-lists ), but now I would like to stay at the two properties, which I will continue to actively use.
1. A variadic parameter can be used as an argument to a function call, cast operations can be applied to it, etc. At the same time, it is revealed depending on the position of the ellipsis, namely, the expression immediately adjacent to the ellipsis is revealed. It sounds incomprehensible, but with an example I think everything will become clear.
In this example, in the function foo2 , since the ellipsis is after the call to bar () , first the bar () function is called for each value from args , and the values returned by bar () get into foo () as arguments . A few more examples.
2. The number of parameters in the pack can be obtained using the sizeof operator ...
The Tuple class is interesting, it seems to me, not so much because variadic templates are now used to write it and create auxiliary functions (you can do without them), but because tuple is a recursive data structure, an alien from another, functional world (hello Haskell), which in turn once again shows how versatile C ++ can be.
I will give a simple implementation of such a class, sketched on the knee, which, nevertheless, shows the basic technique for working with variadic templates - “biting off the head” of the parameter pack and recursive processing of the “tail”, which, by the way, is also widespread in functional languages.
So.
The base class template is never instantiated, therefore without a body.
The main specialization of the template. Here we separate the “head” of the parameter types and the “head” of the arguments passed to us in the constructor. We save this argument in the current class, the rest will be recursively taken by the base ones. We can access the data of the base class by casting “ourselves” to the base type.
The final touch (which is again familiar to functional languages) is to specialize the “bottom” of recursion.
In general, the required minimum has already been written. You can use our class as follows:
However, it’s not very convenient to manually count how many times you need to write .base to get to the element you need, so the get () function template is written in the standard library , which allows you to get the contents of the Nth element of an object of the tuple class . We are forced to wrap a function in a structure in order to circumvent the ban on partial specialization of functions. In this basic template, there is also “biting the head” from the blunt and redirecting to the next getter type with the index value one less both in the case of the element type and, in fact, the function of obtaining this element.
And only when we come to the bottom of the recursion can the first real actions be done. We will take the type of the return value this time from the dull and return the value taken from there.
Well, as is usually accepted, a small helper function is written that saves us from having to manually write the parameters of the structure template.
We use this function.
Unpacking tuple in C ++! What could be cooler =)? This feature seemed so important to the creators of Python that they even introduced special syntax into the language to support this operation. Now we can use it in C ++. It can be implemented in different ways (at least externally, the principle itself is the same everywhere), but I will show here the simplest solution in my opinion. In addition, it resembles what we saw above when implementing getter 'a to retrieve elements of a dummy. Here, property number 1, described in the theory above, will help us. Our unpack function should look something like this
As you remember,
unpack in
But there is one problem, namely, in such a function, it will be necessary to manually specify all the int arguments of the template, and specify them correctly (in the right order and the right amount). It would be very good if we could automate this process. To do this, we will act in a similar way to the extraction of elements from a stupid way.
Here, it seems to me, it is worth explaining in more detail. Let's start with the template options. With F and Tuple, I think everything is clear. The first is responsible for our callable object, the second, in fact, for the tupl, from which we will take objects and palm off callable 'y as arguments to the call. Next is the boolean parameter Enough . It signals whether enough int parameters have already accumulated in ... N and by it we will further specialize our template. Finally, TotalArgs - a value equal to the size of the dull. In the call function, we, as before, redirect the call recursively to the next instantiation of the template.
In this case, in the very first call, the type will be
Finally, we need a specialization in which real actions will be performed, our function will finally be called with the necessary arguments. This specialization is as follows
Also, an auxiliary function does not hurt.
Here, I think, everything is transparent.
You can use this as follows.
The techniques described above allow you to organize lazy, pending calculations. As a private example of such calculations, I will consider a situation where you need to perform some kind of functionality, regardless of how we exit the function, regardless of conditional constructions inside, or whether an exception was raised. This behavior is similar to finally blocks in python and Java, or, for example, in Go, there is a defer statement that provides the behavior described above.
I want to immediately make a reservation that like many other things in C ++, this problem can be solved in various ways, for example, using std :: bind or a lambda that collects arguments and returns another lambda, etc. But also storage of callable object and is stupid with the necessary arguments quite suitable .
Actually, knowing what we already know, the implementation is trivial.
As usual, an auxiliary function
And use
Theory
A template with a variable number of parameters ( variadic template ) is a template of a function or class that accepts the so-called parameter pack . When declaring a template, it looks like this
template struct some_type;
Such a record means that the template can accept 0 or more types as its arguments. In the body of the template, the usage syntax is slightly different.
template // Объявление
void foo(Args… args); // Использование
A call to foo (1,2.3, “abcd”) is instantiated in foo
1. A variadic parameter can be used as an argument to a function call, cast operations can be applied to it, etc. At the same time, it is revealed depending on the position of the ellipsis, namely, the expression immediately adjacent to the ellipsis is revealed. It sounds incomprehensible, but with an example I think everything will become clear.
template
T bar(T t) {/*...*/}
template
void foo(Args... args)
{
//...
}
template
void foo2(Args... args)
{
foo(bar(args)...);
}
In this example, in the function foo2 , since the ellipsis is after the call to bar () , first the bar () function is called for each value from args , and the values returned by bar () get into foo () as arguments . A few more examples.
(const args&..) // -> (const T1& arg1, const T2& arg2, ...)
((f(args) + g(args))...) // -> (f(arg1) + g(arg1), f(arg2) + g(arg2), ...)
(f(args...) + g(args...)) // -> (f(arg1, arg2,...) + g(arg1, arg2, ...))
(std::make_tuple(std::forward(args)...)) // -> (std::make_tuple(std::forward(arg1), std::forward(arg2), ...))
2. The number of parameters in the pack can be obtained using the sizeof operator ...
template
void foo(Args... args)
{
std::cout << sizeof...(args) << std::endl;
}
foo(1, 2.3) // 2
Tuple
The Tuple class is interesting, it seems to me, not so much because variadic templates are now used to write it and create auxiliary functions (you can do without them), but because tuple is a recursive data structure, an alien from another, functional world (hello Haskell), which in turn once again shows how versatile C ++ can be.
I will give a simple implementation of such a class, sketched on the knee, which, nevertheless, shows the basic technique for working with variadic templates - “biting off the head” of the parameter pack and recursive processing of the “tail”, which, by the way, is also widespread in functional languages.
So.
The base class template is never instantiated, therefore without a body.
template
struct tuple;
The main specialization of the template. Here we separate the “head” of the parameter types and the “head” of the arguments passed to us in the constructor. We save this argument in the current class, the rest will be recursively taken by the base ones. We can access the data of the base class by casting “ourselves” to the base type.
template
struct tuple : tuple
{
tuple(Head h, Tail... tail)
: tuple(tail...), head_(h)
{}
typedef tuple base_type;
typedef Head value_type;
base_type& base = static_cast(*this);
Head head_;
};
The final touch (which is again familiar to functional languages) is to specialize the “bottom” of recursion.
template<>
struct tuple<>
{};
In general, the required minimum has already been written. You can use our class as follows:
tuple t(12, 2.34, 89);
std::cout << t.head_ << " " << t.base.head_ << " " << t.base.base.head_ << std::endl;
However, it’s not very convenient to manually count how many times you need to write .base to get to the element you need, so the get () function template is written in the standard library , which allows you to get the contents of the Nth element of an object of the tuple class . We are forced to wrap a function in a structure in order to circumvent the ban on partial specialization of functions. In this basic template, there is also “biting the head” from the blunt and redirecting to the next getter type with the index value one less both in the case of the element type and, in fact, the function of obtaining this element.
template
struct getter
{
typedef typename getter::return_type return_type;
static return_type get(tuple t)
{
return getter::get(t);
}
};
And only when we come to the bottom of the recursion can the first real actions be done. We will take the type of the return value this time from the dull and return the value taken from there.
template
struct getter<0, Head, Args...>
{
typedef typename tuple::value_type return_type;
static return_type get(tuple t)
{
return t.head_;
}
};
Well, as is usually accepted, a small helper function is written that saves us from having to manually write the parameters of the structure template.
template
typename getter::return_type
get(tuple t)
{
return getter::get(t);
}
We use this function.
test::tuple t(12, 2.34, 89);
std::cout << t.head_ << " " << t.base.head_ << " " << t.base.base.head_ << std::endl;
std::cout << get<0>(t) << " " << get<1>(t) << “ “ << get<2>(t) << std::endl;
Unpacking
Unpacking tuple in C ++! What could be cooler =)? This feature seemed so important to the creators of Python that they even introduced special syntax into the language to support this operation. Now we can use it in C ++. It can be implemented in different ways (at least externally, the principle itself is the same everywhere), but I will show here the simplest solution in my opinion. In addition, it resembles what we saw above when implementing getter 'a to retrieve elements of a dummy. Here, property number 1, described in the theory above, will help us. Our unpack function should look something like this
template
auto call(F f, Tuple&& t)
{
return f(std::get(std::forward(t))...);
}
As you remember,
f(std::get(std::forward(t))...);
unpack in
f(std::get(std::forward(t)), std::get(std::forward(t)), ...)
But there is one problem, namely, in such a function, it will be necessary to manually specify all the int arguments of the template, and specify them correctly (in the right order and the right amount). It would be very good if we could automate this process. To do this, we will act in a similar way to the extraction of elements from a stupid way.
template
struct call_impl
{
auto static call(F f, Tuple&& t)
{
return call_impl::call(f, std::forward(t));
}
};
Here, it seems to me, it is worth explaining in more detail. Let's start with the template options. With F and Tuple, I think everything is clear. The first is responsible for our callable object, the second, in fact, for the tupl, from which we will take objects and palm off callable 'y as arguments to the call. Next is the boolean parameter Enough . It signals whether enough int parameters have already accumulated in ... N and by it we will further specialize our template. Finally, TotalArgs - a value equal to the size of the dull. In the call function, we, as before, redirect the call recursively to the next instantiation of the template.
In this case, in the very first call, the type will be
call_impl // (N… - пусто, sizeof...(N) = 0)
in the second call_impl // (N… =0, sizeof...(N) = 1)
etc. that is exactly what we need. Finally, we need a specialization in which real actions will be performed, our function will finally be called with the necessary arguments. This specialization is as follows
template
struct call_impl
{
auto static call(F f, Tuple&& t)
{
return f(std::get(std::forward(t))...);
}
};
Also, an auxiliary function does not hurt.
template
auto call(F f, Tuple&& t)
{
typedef typename std::decay::type type;
return call_impl::value,
std::tuple_size::value
>::call(f, std::forward(t));
}
Here, I think, everything is transparent.
You can use this as follows.
int foo(int i, double d)
{
std::cout << "foo: " << i << " " << d << std::endl;
return i;
}
std::tuple t1(1, 2.3);
std::cout << call(foo, t1) << std::endl;
Defer
The techniques described above allow you to organize lazy, pending calculations. As a private example of such calculations, I will consider a situation where you need to perform some kind of functionality, regardless of how we exit the function, regardless of conditional constructions inside, or whether an exception was raised. This behavior is similar to finally blocks in python and Java, or, for example, in Go, there is a defer statement that provides the behavior described above.
I want to immediately make a reservation that like many other things in C ++, this problem can be solved in various ways, for example, using std :: bind or a lambda that collects arguments and returns another lambda, etc. But also storage of callable object and is stupid with the necessary arguments quite suitable .
Actually, knowing what we already know, the implementation is trivial.
template
struct defer
{
defer(F f, Args&&... args) :
f_(f), args_(std::make_tuple(std::forward(args)...))
{}
F f_;
std::tuple args_;
~defer()
{
try
{
call(f_, args_);
}
catch(...)
{}
}
};
As usual, an auxiliary function
template
defer make_deferred(F f, Args&&... args)
{
return defer(f, std::forward(args)...);
}
And use
auto d = make_deferred(foo, 1 ,2);