Last Update:
How to Pass a Variadic Pack as the First Argument of a Function in C++
Table of Contents
Variadic templates and argument packs which are available since C++11 give flexibility in situations when you don’t know the number of inputs upfront. However, they are limited and can only appear at the end of the type sequence.
Have a look at today’s blog post from Jonathan Boccara, who describes a technique that might improve this situation. We’ll also look into the future.
This is a guest post from Jonathan Boccara:
Jonathan runs the Fluent C++ blog. Jonathan is a passionate C++ developer, writer, blogger, and conference speaker. You can find him online on Twitter and on Fluent C++.
Let’s start
In C++, the normal way of passing a variadic pack of parameters is at the last position of the function:
template<typename A, typename... Bs>
void f(A&& a, Bs&&... bs)
{
// implementation of f...
}
But what if the last position doesn’t make sense for the meaning of your function?
Expressive order of parameters
The order of parameters in a function interface carries meaning about what the function is doing. Indeed, there are several logical orders that make sense for a function’s parameters.
One of them, suggested in Code Complete, is in,inout,out: first the inputs of the function, then the parameters that the function reads and modifies (if any), and last the outputs of the function (the objects that the function modifies without reading).
Let’s say that we’d like to follow this convention, but that the inputs are in the variadic pack. This interface is then the wrong way around:
template<typename Output, typename... Inputs>
void f(Output& output, Inputs const&... inputs)
{
// implementation of f...
}
Note that in general, the best practice is to produce outputs via the return type, and not to take them as parameters. The case where the above interface makes sense is when the function does not create the output itself, in which case it is an input-output parameter rather than an output.
In this case, we force the callers to pass the inputs last and the outputs first:
f(output, input1, input2, input3);
But we’d rather have a call site that looks like this:
f(input1, input2, input3, output);
I encountered this need in the pipes library, with the send function. send can send any number of values to a pipeline:
send(1, 2, 3, pipeline);
Since 1, 2 and 3 are coming into the pipeline, to me the above call site reads like: “send 1, 2, and 3 to pipeline. This is more expressive than if the variadic pack was located at the end:
send(pipeline, 1, 2, 3);
Since the usual way in C++ is to have the variadic pack at the end, how do we turn around the function parameters to let the caller pass the variadic pack first?
Passing the variadic pack first
To pass the variadic pack first, we can use the following trick: wrap the function with the “technical” interface (variadic pack at the end) with another one that you can call with the “logical” interface (variadic pack at the beginning).
We’ll proceed in three steps:
- Receiving all function parameters (the “logical” interface), starting with the inputs
- Extracting the inputs and the output
- Calling the function with outputs first (the “technical” interface)
Receiving all parameters
Since we can’t pass the variadic pack first, we’re going to have one variadic pack containing all the parameters, starting with the inputs and followed by the output:
// usage: f(input1, input2, input3, output);
template<typename... InputsThenOutput>
void f(InputsThenOutput&&... inputsThenOutput)
{
We can add a comment like the one above to clarify how the interface should be called.
This interface doesn’t separate out its parameters. This is a drawback, but one that will allow having the call site we want. We’ll discuss later on whether this technique is worth the change of interface.
Let’s implement this function. It wraps its arguments into a tuple of references, and indicates where the inputs are located: in our case at all the positions except the last one:
// usage: f(input1, input2, input3, output);
template<typename... InputsThenOutput>
void f(InputsThenOutput&&... inputsThenOutput)
{
f(std::forward_as_tuple(inputsThenOutput...), std::make_index_sequence<sizeof...(inputsThenOutput) - 1>{});
}
std::forward_as_tuple
constructs the tuple of references to the function arguments, so we can pass them on. And std::make_index_sequence
constructs a list of indexes from 0 to its parameters count.
Extracting the inputs and outputs
Now we have a tuple with all the inputs followed by the output, and a list of indexes indicating the positions of the inputs.
We can easily find the position of the output: it’s the last one. We can then extract this output from the tuple, then extract the inputs, and call the “technical” version of f with the outputs first:
template<typename... InputsThenOutput, size_t... InputIndexes>
void f(std::tuple<InputsThenOutput...> inputsThenOutputs, std::index_sequence<InputIndexes...>)
{
auto constexpr OutputIndex = sizeof...(InputsThenOutput) - 1;
fOutputFirst(std::get<OutputIndex>(inputsThenOutputs), std::get<InputIndexes>(inputsThenOutputs)...);
}
Implementing the function
fOutputFirst
is the function that does the real job, because it has access to the individual its parameters (but has the variadic pack at the end):
template<typename Output, typename... Inputs>
void fOutputFirst(Output& output, Inputs const&... inputs)
{
// implementation of f...
}
We can also keep calling it f
and put it in another namespace, as we see in the recap just below.
Putting it all together
Here is all the code together, if you’d like to copy it and adapt it for your need. The first two functions are not supposed to be called directly, so we can put them into another namespace to make this clearer:
namespace detail
{
template<typename Output, typename... Inputs>
void tempFunc(Output& output, Inputs const&... inputs)
{
// implementation of f
}
template<typename... InputsThenOutput, size_t... InputIndexes>
void tempFunc(std::tuple<InputsThenOutput...> inputsThenOutputs, std::index_sequence<InputIndexes...>)
{
auto constexpr OutputIndex = sizeof...(InputsThenOutput) - 1;
detail::tempFunc(std::get<OutputIndex>(inputsThenOutputs), std::get<InputIndexes>(inputsThenOutputs)...);
}
}
// usage: tempFunc(input1, input2, input3, output);
template<typename... InputsThenOutput>
void tempFuncInputsThenOutput&&... inputsThenOutput)
{
detail::tempFunc(std::forward_as_tuple(inputsThenOutput...), std::make_index_sequence<sizeof...(inputsThenOutput) - 1>{});
}
Optimizing for expressiveness
This technique optimizes the expressiveness of the call site at the expense of the one of the interface and implementation. Indeed, the interface needs naming and a comment to help clarify how to use it, and the implementation has more code to turn the parameters around.
Is it worth it? If the function is called at many places in the code and if the parameter order makes more sense, then it can be worth considering applying this technique. I think the send function of the pipes library is such a case, for example.
To decide in the general case, you have to weigh to pros and the cons, and identify what part of the code you want to make the most expressive.
Improvements in C++23?
The C++ Committee is aware of the limitations of the variadic pack, and there’s a chance it will be fixed in some future version of the C++ Standard. Have a look at this article from Corentin: Non-terminal variadic template parameters | cor3ntin.
Some notes:
- Having non-terminal variadic packs would allow
std::source_location
(from C++20) to nicely sit as the default argument at the end of somelog(...)
function. - You can play with the proposal at Compiler Explorer branch
I've prepared a valuable bonus for you!
Learn all major features of recent C++ Standards on my Reference Cards!
Check it out here: