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 << \n;}template<typename T, typename ...Args>void writeln(T firstArg, Args... extraArgs){    std::cout << firstArg;    writeln(std::forward<Args>(extraArgs)...);}Usage example:writeln(hello , world , 42, \t, 3.141592);writeln(); // prints just a newlineNext, 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 & s){    return s;}std::string format(){    return ;}template<typename T, typename ...Args>std::string format(T firstArg, Args... extraArgs){    using namespace std;    std::string s = to_string(firstArg);    s += format(std::forward<Args>(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.
writeln() and format() with variadic templates
c++;c++11;formatting;template meta programming;variadic
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&&:template<typename T, typename ...Args>void writeln(T firstArg, Args&&... extraArgs)template<typename T, typename ...Args>std::string format(T firstArg, Args&&... extraArgs)As a cut down example of the difference this makes, try this example:#include <memory>#include <iostream>struct s{    s()    { }    s(s&& )    {        std::cout << Move\n;    }    s(const s& )    {        std::cout << Copy\n;    }};template <typename T>void do_forward(T&& v){    sink(std::forward<T>(v));}template <typename U>void sink(U&& u){    std::cout << In sink\n;}int main(){    s something;    do_forward(something);}If I run this, the output is:In sinkIf I change the signature of do_foward and sink to:template <typename T>void do_forward(T v)template <typename U>void sink(U u)The output is:CopyMoveIn sinkIf 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).