What is the easiest way to print a variadic parameter pack using std::ostream?

33.8k views Asked by At

What is the easiest way to print a parameter pack, separated by commas, using std::ostream?

Example:

template<typename... Args>
void doPrint(std::ostream& out, Args... args){
   out << args...; // WRONG! What to write here?
}

// Usage:
int main(){
   doPrint(std::cout,34,"bla",15); // Should print: 34,bla,15
}

Note: It may be assumed that a corresponding overload of the << operator is available for all types of the parameter pack.

6

There are 6 answers

10
Piotr Skotnicki On BEST ANSWER

Without recursive calls and commas where you wanted.

In / through parameter pack expansion:

template <typename Arg, typename... Args>
void doPrint(std::ostream& out, Arg&& arg, Args&&... args)
{
    out << std::forward<Arg>(arg);
    using expander = int[];
    (void)expander{0, (void(out << ',' << std::forward<Args>(args)), 0)...};
}

DEMO


In using fold expressions:

template <typename Arg, typename... Args>
void doPrint(std::ostream& out, Arg&& arg, Args&&... args)
{
    out << std::forward<Arg>(arg);
    ((out << ',' << std::forward<Args>(args)), ...);
}

DEMO 2

0
Maxim Egorushkin On

Parameter pack expansion only works in plain function calls, not for infix operators. Hence, you need to convert s << x syntax into plain function call syntax f(s, x):

template<class Head>
void print_args_(std::ostream& s, Head&& head) {
    s << std::forward<Head>(head);
}

template<class Head, class... Tail>
void print_args_(std::ostream& s, Head&& head, Tail&&... tail) {
    s << std::forward<Head>(head);
    print_args_(s, std::forward<Tail>(tail)...);
}

template<class... Args>
void print_args(Args&&... args) {
    print_args_(std::cout, std::forward<Args>(args)...);
}
2
Kerrek SB On

The usual answer is to define two separate overloads, with an empty one for the base case:

// base case
void doPrint(std::ostream& out) {}

template <typename T, typename... Args>
void doPrint(std::ostream& out, T t, Args... args)
{
    out << t;                // add comma here, see below
    doPrint(out, args...);
}

Of course in real code I wouldn't make copies of the arguments each time and instead use forwarding references, but you get the idea.

If you want to add commas after every item, even after the last one, just replace out << t with out << t << ','.

If you only want commas on the inside, not past the last element, you need a separate one-argument overload which doesn't print the comma, and a generic overload take two distinct arguments before the pack, i.e:

template <typename T>
void doPrint(std::ostream& out, T t)
{
    out << t;
}

template <typename T, typename U, typename... Args>
void doPrint(std::ostream& out, T t, U u, Args... args)
{
    out << t << ',';
    doPrint(out, u, args...);
}
1
eloiluiz On

I know its an old question, but it helped me a lot with my problem. I created an utility class based on this post answers and I'd like to share my result.

Considering we use C++11 or latter C++ versions, this class provides print and println functions to compose strings before calling the standard output stream and avoid concurrency problems. These are variadic functions which use templates to print different data types.

You can check its use in a producer-consumer problem on my github: https://github.com/eloiluiz/threadsBar

So, here is my code:

class Console {
private:
    Console() = default;

    inline static void innerPrint(std::ostream &stream) {}

    template<typename Head, typename... Tail>
    inline static void innerPrint(std::ostream &stream, Head const head, Tail const ...tail) {
        stream << head;
        innerPrint(stream, tail...);
    }

public:
    template<typename Head, typename... Tail>
    inline static void print(Head const head, Tail const ...tail) {
        // Create a stream buffer
        std::stringbuf buffer;
        std::ostream stream(&buffer);
        // Feed input parameters to the stream object
        innerPrint(stream, head, tail...);
        // Print into console and flush
        std::cout << buffer.str();
    }

    template<typename Head, typename... Tail>
    inline static void println(Head const head, Tail const ...tail) {
        print(head, tail..., "\n");
    }
};

I like this alternative better than overloading the << operator or using complex stream functions. Its an recursive approach, but not so hard to understand.

0
anton_rh On

The generic form that works with std::wostream as well:

template <typename CharT, typename Traits>
std::basic_ostream<CharT, Traits> &
Print(std::basic_ostream<CharT, Traits> &out)
{
    return out;
}

template <typename CharT, typename Traits, typename T>
std::basic_ostream<CharT, Traits> &
Print(std::basic_ostream<CharT, Traits> &out, T &&t)
{
    return (out << std::forward<T>(t));
}

template <typename CharT, typename Traits, typename T, typename... Args>
std::basic_ostream<CharT, Traits> &
Print(std::basic_ostream<CharT, Traits> &out, T &&t, Args &&...args)
{
    return Print( Print(out, std::forward<T>(t)), std::forward<Args>(args)... );
}

I couldn't make it work with with std::endl in the common case (it is possible to handle std::endl in specific cases like when it is the first or the last argument, but not in the common case, especially if there are multiple std::endl in a single call). You can still use '\n' instead or use std::endl with template arguments specified if you really need std::endl:

Print(std::cout, "hello world", std::endl<char, std::char_traits<char>>);

The differences between std::endl and '\n'

  • If the stream is working in binary mode then '\n' isn't converted to the line ending format of the platform the code compiled for (but in text mode it is still converted).
  • '\n' doesn't flush the stream with std::flush (but it still flushes the std::cout if the program is running on a terminal)

So for me it is OK to use '\n', or maybe even preferred.

Using some other IO manipulators is still possible:

Print(std::cout, std::hex, 11, '\n');

I also implemented sprintf counterpart that works with variadic templates and returns std::string:

template <typename CharT = char, typename Traits = std::char_traits<CharT>, typename... Args>
std::basic_string<CharT, Traits>
SPrint(Args &&...args)
{
    std::basic_stringstream<CharT, Traits> ss;
    Print(ss, std::forward<Args>(args)...);
    return std::move(ss.str());
}

Here is some demos.

1
M.M On

In C++17, there will be an easier way (as hinted at by Kerrek SB in comments; this was actually present in N4606, the first post-C++14 draft), called fold expressions:

The code would be:

(out << ... << args);

and the pattern expression op ... op parameter-pack is called a binary left fold, whose definition is equivalent to ((( expression op arg1) op arg2) op arg3) .... op argN.

I think the outer parentheses are not strictly necessary for an expression-statement like this, but if the fold expression is an operand of another operator then they are either required, or a very good idea :)