44

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.

gexicide
  • 38,535
  • 21
  • 92
  • 152
  • 7
    In C++17, you'd say `(out << ... <<< args);`. – Kerrek SB Dec 09 '14 at 10:03
  • Note: This is not a duplicate - `foo << X << Y;` generally is not the same as `foo << X; foo << Y;` especially when `foo` has side effects such as opening a file on disk. – MSalters May 04 '17 at 12:32
  • @MSalters also, `foo << X << Y` does not specify evaluation order of `X` and `Y` (prior to C++17, that is) – M.M May 04 '17 at 12:38

6 Answers6

71

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

Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
  • 1
    This one is the easiest. It is just that it looks weird, hehe. – Germán Diago Dec 09 '14 at 10:29
  • 7
    @GermánDiago http://en.cppreference.com/w/cpp/language/parameter_pack gives a similar example: `int dummy[sizeof...(Ts)] = { (std::cout << args, 0)... };` – Maxim Egorushkin Dec 09 '14 at 22:52
  • 2
    As a side note, you can skip the `using` declaration and just write `(void)(int[]){(std::cout << ',' << std::forward(args)), 0)...};`. You also don't need the first zero in the braces or the inner `void` cast. – Alexander Revo May 18 '16 at 17:06
  • Btw, my understanding of forwarding is not exactly rock-solid, but there is no reason at all to use `std::forward` to insert a value into `ostream`, or is there? – Alexander Revo May 18 '16 at 17:15
  • 1
    @AlexanderRevo *inserting* into `ostream` means passing the argument to some overload of `operator<<`. you never know what parameter type the operator expects – Piotr Skotnicki May 20 '16 at 13:34
  • 11
    My C++ must be getting out-dated. I can't parse that at all. – Ian Gilham Jan 13 '17 at 13:09
  • 1
    @IanGilham appologise if my answer is not clear. Please find an extensive explanation [here](http://stackoverflow.com/a/25683817/3953764) – Piotr Skotnicki Jan 13 '17 at 13:16
  • Is there some way to make this work for the one-parameter case as well (i.e., `doPrint( std::cout, 1 )`)? (Obviously we don't want the fold expression at all in that case, but how to magick that into being?) – DevSolar Dec 04 '18 at 14:12
  • @PiotrSkotnicki: Ah. I was seeing an error, but that was because I appended a `<< std::endl` to the fold expression. D'uh. ;-) – DevSolar Dec 04 '18 at 14:15
  • @PiotrSkotnicki impressive, why do I need the left most cast to void in the C++11 version? – Larytet Feb 06 '21 at 14:42
36

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 :)

M.M
  • 138,810
  • 21
  • 208
  • 365
11

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...);
}
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • Can you elaborate a bit on the forwarding references? I am not too firm in that C++11 stuff, yet, and copying the parameters around really seems to be an overkill. – gexicide Dec 09 '14 at 09:20
  • @gexicide: thousands of duplicates on this site, just search around. You're looking for `Args &&... args` and `std::forward(args)...`. – Kerrek SB Dec 09 '14 at 09:23
7

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)...);
}
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
1

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.

eloiluiz
  • 21
  • 5
  • 1
    I would change the constructor in `= delete`. Since there are only static method, it's pointless instantiate a `Console` object – Moia Dec 03 '18 at 08:33
1

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.

anton_rh
  • 8,226
  • 7
  • 45
  • 73