Trevor Hickey
Trevor Hickey

Reputation: 37834

How do I write variadic templates, that can't accept zero arguments?

Here is a variadic template that prints parameters.

#include <string>
#include <iostream>

void Output() {
    std::cout<<std::endl;
}

template<typename First, typename ... Strings>
void Output(First arg, const Strings&... rest) {
    std::cout<<arg<<" ";
    Output(rest...);
}

int main() {
    Output("I","am","a","sentence");
    Output("Let's","try",1,"or",2,"digits");
    Output(); //<- I do not want this to compile, but it does.

    return 0;
}

Is there a way to get this functionality without having the "no parameter" call work, and without having to write two functions every time?

Upvotes: 1

Views: 187

Answers (3)

TAS
TAS

Reputation: 2079

As far as I see there are two questsions:

  1. How to avoid Output() calls with no parameters.
  2. Is there a simpler way to end the compile time recursion?

My solution to item 1 is as follows:

template<typename T>
void Output(const T & string) {
    std::cout<<string<<std::endl;
}

template<typename First, typename ... Strings>
void Output(const First & arg, const Strings & ... rest) {
    std::cout<<arg<<" ";
    Output(rest...);
}

Basically, instead of ending the recursion when the template list is empty, I end it when it only contains one type. There is one difference between the above and the code from the question: if does not output any space after the last item. Instead it just outputs the newline.

For question number two see the answer by Daniel Frey above. I really liked this solution, although it took some time to grasp it (and I upvoted the answer). At the same time I find that it makes the code harder to read/understand and therefore harder to maintain. Currently I would not not use that solution in anything but small personal code snippets.

Upvotes: 2

Daniel Frey
Daniel Frey

Reputation: 56863

You might want to keep the separation of the first and the rest of the parameters, you can use:

template<typename First, typename ... Rest>
void Output(First&& first, Rest&&... rest) {
    std::cout << std::forward<First>(first);
    int sink[]{(std::cout<<" "<<std::forward<Rest>(rest),0)... };
    (void)sink; // silence "unused variable" warning
    std::cout << std::endl;
}

Note that I used perfect forwarding to avoid copying any parameters. The above has the additional benefit to avoid recursion and therefore is likely to produce better (faster) code.

The way I wrote sink also guarantees that the expressions expanded from rest are evaluated left-to-right - which is important when compared to the naïve approach of just writing a helper function template<typename...Args>void sink(Args&&...){}.

Live example

Upvotes: 3

aaronman
aaronman

Reputation: 18750

Call the function from a forwarding type function and have a static_assert like this:

template <typename ... Args>                                                       
void forwarder(Args ... args) {                                                    
    static_assert(sizeof...(args),"too small");                                    
    Output(args...);                                                               
}  

Upvotes: 2

Related Questions