user44168
user44168

Reputation: 445

How to write a variadic function without parameter pack?

suppose I have a template

template<typename Bar>
Result foo(const Input& input);

and I instead want to create a template foo() that would get many Bar template arguments and many Input's and put the Result together.

I wrote a version with a container of inputs:

template<typename Bar>
Result foo(const std::vector<Input>& inputs);

template<typename Bar1, typename Bar2, typename ... Bars>
Result foo(const std::vector<Input>& inputs) {
    // calls foo<Bar2, Bars ...>(shorter_inputs);
}

However, the version above cannot check at compile time if the length of the input matches the number of template arguments. Also, I want a version that would simply take the inputs, without the need for a container:

foo<Bar1, Bar2>(input1, input2);
// instead of
// foo<Bar1, Bar2>({input1, input2});

My attempts to write something like this

template<typename Bar1, typename Bar2, typename ... Bars>
Result bar(const Input& in1, const Input& in2, const Input& ... inputs)

were rejected by the compiler, because ... only works with a parameter pack. Is it possible to write such a variadic function in C++, without resorting to va_args?

Upvotes: 2

Views: 142

Answers (2)

r3mus n0x
r3mus n0x

Reputation: 6144

Just use two parameter packs:

template<typename Bar, typename... Bars, typename Input, typename... Inputs>
std::string foo(const Input &input, const Inputs &... inputs)
{
    std::string result(Bar::get_name());
    result.append(input);

    if constexpr (sizeof...(inputs) > 0)
        result.append(foo<Bars...>(inputs...));

    return result;
}

Upvotes: 3

max66
max66

Reputation: 66210

Not sure to understand what do you want...

If Result and Input are types and not template parameters and you want a template foo() that receive as many Input objects a template parameters, you can start from a custom template parameter GetFirst to select the first template parameter in a variadic list

template <typename T0, typename ...>
struct GetFirst
 { using type = T0; };

and write foo() as follows

template <typename ... Bars>
Result foo (typename GetFirst<Input, Bars>::type const & ... is)
 { 
   // something with is...
   return {};
 }

So you have

//foo<long, long long>(Input{}, Input{}, Input{}); // compilation error

foo<int, long, long long>(Input{}, Input{}, Input{}); // compiles

//foo<int, long, long long>(Input{}, Input{}); // compilation error

If you want to manage the single is... recursively, you can write foo() as follows

template <typename, typename ... Bars>
Result foo (Input const & i0,
            typename GetFirst<Input, Bars>::type const & ... is)
 {
   // do something with i0

   return foo<Bars...>(is...);
 }

but you need also a ground case template foo() to terminate the recursion; I propose a foo() that receive a template not-type parameter with a default value (to intercept the Bars... when the list is empty)

template <int = 0>
Result foo ()
 { return {}; }

The following is a full, maybe silly, but compiling example

struct Input  { };
struct Result { };

template <typename T0, typename ...>
struct GetFirst
 { using type = T0; };

// ground case
template <int = 0>
Result foo ()
 { return {}; }

// recursive case
template <typename, typename ... Bars>
Result foo (Input const & i0,
            typename GetFirst<Input, Bars>::type const & ... is)
 {
   // do something with i0

   return foo<Bars...>(is...);
 }

int main()
 {
   //foo<long, long long>(Input{}, Input{}, Input{}); // compilation error

   foo<int, long, long long>(Input{}, Input{}, Input{}); // compiles

   //foo<int, long, long long>(Input{}, Input{}); // compilation error
 }

Upvotes: 1

Related Questions