writeln() and format() with variadic templates I wanted to get better acquainted with variadic templates, so I decide to try to implement a function like D's writeln(), just for fun.

void writeln()
{
    std::cout &lt;&lt; "\n";
}

template&lt;typename T, typename ...Args&gt;
void writeln(T firstArg, Args... extraArgs)
{
    std::cout &lt;&lt; firstArg;
    writeln(std::forward&lt;Args&gt;(extraArgs)...);
}


Usage example:

writeln("hello ", "world ", 42, "\t", 3.141592);
writeln(); // prints just a newline


Next, I implemented a format() function, which writes to a string instead of cout:

// Need this because there is no to_string for string in the std namespace.
std::string to_string(const std::string &amp; s)
{
    return s;
}

std::string format()
{
    return "";
}

template&lt;typename T, typename ...Args&gt;
std::string format(T firstArg, Args... extraArgs)
{
    using namespace std;
    std::string s = to_string(firstArg);
    s += format(std::forward&lt;Args&gt;(extraArgs)...);
    return s;
}

// sample:
std::string s = format("hello ", "world ", 42, "\t", 3.141592);


It is uses std::to_string() for the native types. If I want to print custom types, then I can define a local to_string() and the overload resolution should find it.

My concerns are:


I haven't had many chances to use variadic templates so far, so I might be missing some caveat here. I was expecting it to be more complicated... Did I miss some corner case?
Is my use of std::forward appropriate?
Any other comments and suggestion are very welcome.
The biggest thing that sticks out at me is that you're passing all of your Args... parameters by value, thus pretty much making std::forward useless here. To make use of std::forward, the reference type needs to be deduced from the calling context. By itself, std::forward really doesn't do anything except a static_cast to the deduced reference type.
Basically, everywhere you have Args should be passed by Args&amp;&amp;:
template&lt;typename T, typename ...Args&gt;
void writeln(T firstArg, Args&amp;&amp;... extraArgs)

template&lt;typename T, typename ...Args&gt;
std::string format(T firstArg, Args&amp;&amp;... extraArgs)

As a cut down example of the difference this makes, try this example:
#include &lt;memory&gt;
#include &lt;iostream&gt;

struct s
{
    s()
    { }
    
    s(s&amp;&amp; )
    {
        std::cout &lt;&lt; &quot;Move\n&quot;;
    }
    
    s(const s&amp; )
    {
        std::cout &lt;&lt; &quot;Copy\n&quot;;
    }
};

template &lt;typename T&gt;
void do_forward(T&amp;&amp; v)
{
    sink(std::forward&lt;T&gt;(v));
}

template &lt;typename U&gt;
void sink(U&amp;&amp; u)
{
    std::cout &lt;&lt; &quot;In sink\n&quot;;
}

int main()
{
    s something;
    do_forward(something);
}

If I run this, the output is:

In sink

If I change the signature of do_foward and sink to:
template &lt;typename T&gt;
void do_forward(T v)

template &lt;typename U&gt;
void sink(U u)

The output is:

Copy
Move
In sink

If we change main to just pass an actual rvalue reference instead of an lvalue:
int main()
{
    do_forward(s{});
}

We remove the first copy operation (from do_forward) but still have a move operation (in sink).