PREMISE:
After playing around with variadic templates a little bit, I realized that achieving anything which goes slightly beyond the trivial meta-programming tasks soon becomes pretty cumbersome. In particular, I found myself wishing for a way to perform generic operations over an argument pack such as iterate, split, loop in a std::for_each
-like fashion, and so on.
After watching this lecture by Andrei Alexandrescu from C++ and Beyond 2012 on the desirability of static if
into C++ (a construct borrowed from the D Programming Language) I had the feeling that some sort of static for
would come handy as well - and I feel more of these static
constructs could bring benefit.
So I started wondering if there is a way to achieve something like this for argument packs of a variadic template function (pseudo-code):
template<typename... Ts>
void my_function(Ts&&... args)
{
static for (int i = 0; i < sizeof...(args); i++) // PSEUDO-CODE!
{
foo(nth_value_of<i>(args));
}
}
Which would get translated at compile-time into something like this:
template<typename... Ts>
void my_function(Ts&&... args)
{
foo(nth_value_of<0>(args));
foo(nth_value_of<1>(args));
// ...
foo(nth_value_of<sizeof...(args) - 1>(args));
}
In principle, static_for
would allow for even more elaborate processing:
template<typename... Ts>
void foo(Ts&&... args)
{
constexpr s = sizeof...(args);
static for (int i = 0; i < s / 2; i++)
{
// Do something
foo(nth_value_of<i>(args));
}
static for (int i = s / 2; i < s; i++)
{
// Do something different
bar(nth_value_of<i>(args));
}
}
Or for a more expressive idiom like this one:
template<typename... Ts>
void foo(Ts&&... args)
{
static for_each (auto&& x : args)
{
foo(x);
}
}
RELATED WORK:
I did some search on the Web and found out that something does indeed exist:
- This link describes how to convert a parameter pack into a Boost.MPL vector, but that only goes half the way (if not less) towards the goal;
- this question on SO seems to call for a similar and slightly related meta-programming feature (splitting an argument pack into two halves) - actually, there are several questions on SO which seem to be related to this issue, but none of the answer I have read solves it satisfactorily IMHO;
- Boost.Fusion defines algorithms for converting an argument pack into a tuple, but I would prefer:
- not to create unnecessary temporaries to hold arguments that can (and should be) perfectly forwarded to some generic algorithms;
- have a small, self-contained library to do that, while Boost.Fusion is likely to include way more stuff than is needed to address this issue.
QUESTION:
Is there a relatively simple way, possibly through some template meta-programming, to achieve what I am looking for without incurring in the limitations of the existing approaches?
Since I was not happy with what I found, I tried to work out a solution myself and ended up writing a small library which allows formulating generic operations on argument packs. My solution has the following features:
I will first show what can be done with the library, then post its implementation.
USE CASES
Here is an example of how the
for_each_in_arg_pack()
function can be used to iterate through all the arguments of a pack and pass each argument in input to some client-supplied functor (of course, the functor must have a generic call operator if the argument pack contains values of heterogenous types):The
print
functor above can also be used in more complex computations. In particular, here is how one would iterate on a subset (in this case, a sub-range) of the arguments in a pack:Sometimes, one may just want to forward a portion of an argument pack to some other variadic functor instead of iterating through its elements and pass each of them individually to a non-variadic functor. This is what the
forward_subpack()
algorithm allows doing:For more specific tasks, it is of course possible to retrieve specific arguments in a pack by indexing them. This is what the
nth_value_of()
function allows doing, together with its helpersfirst_value_of()
andlast_value_of()
:If the argument pack is homogeneous on the other hand (i.e. all arguments have the same type), a formulation such as the one below might be preferable. The
is_homogeneous_pack<>
meta-function allows determining whether all the types in a parameter pack are homogeneous, and is mainly meant to be used instatic_assert()
statements:Finally, since lambdas are just syntactic sugar for functors, they can be used as well in combination with the algorithms above; however, until generic lambdas will be supported by C++, this is only possible for homogeneous argument packs. The following example also shows the usage of the
homogeneous-type<>
meta-function, which returns the type of all arguments in a homogeneous pack:This is basically what the library allows doing, but I believe it could even be extended to carry out more complex tasks.
IMPLEMENTATION
Now comes the implementation, which is a bit tricky in itself so I will rely on comments to explain the code and avoid making this post too long (perhaps it already is):
CONCLUSION
Of course, even though I provided my own answer to this question (and actually because of this fact), I am curious to hear if alternative or better solutions exist which I have missed - apart from the ones mentioned in the "Related Works" section of the question.
Using an enumerate solution (ala Python).
Usage:
Code:
The range builder (pilferred from your solution):
Let me post this code, based on the discussion:
I checked the generated code with
g++ -std=c++11 -O1
andmain
only contains 3 calls toprint
, there is no trace of the expansion helpers.After reading a few other posts and tinkering for a while I came up with the following (somewhat similar to the above, but the implementation is a little different). I wrote this using the Visual Studio 2013 compiler.
Usage using a lambda expression -
The downside when using a lambda is the parameters must be of the same type declared in the lambda's parameter list. This means it will only work with one type. If you want to use a templated function, you can use the next example.
Usage using struct wrapper functor -
This allows you to pass in any types you'd like and operate on them using the functor. I found this pretty clean and works nicely for what I wanted. You can also use it with a function parameter pack like this -
Here is the implementation -
Hope people find this useful :-)
The ... notation does have some interesting options, like:
Unfortunately, I don't know of any way to enforce the order in which the print functions are called (reverse, on my compiler). Note that print needs to return something.
This trick can be useful if you don't care about order.