HiveBrain v1.2.0
Get Started
← Back to all entries
snippetcppMinor

writeln() and format() with variadic templates

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
formatwithandtemplatesvariadicwriteln

Problem

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 
void writeln(T firstArg, Args... extraArgs)
{
    std::cout (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 & s)
{
    return s;
}

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

template
std::string format(T firstArg, Args... extraArgs)
{
    using namespace std;
    std::string s = to_string(firstArg);
    s += format(std::forward(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.

Solution

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
void writeln(T firstArg, Args&&... extraArgs)

template
std::string format(T firstArg, Args&&... extraArgs)


As a cut down example of the difference this makes, try this example:

#include 
#include 

struct s
{
    s()
    { }
    
    s(s&& )
    {
        std::cout 
void do_forward(T&& v)
{
    sink(std::forward(v));
}

template 
void sink(U&& u)
{
    std::cout << "In sink\n";
}

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 
void do_forward(T v)

template 
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).

Code Snippets

template<typename T, typename ...Args>
void writeln(T firstArg, Args&&... extraArgs)

template<typename T, typename ...Args>
std::string format(T firstArg, Args&&... extraArgs)
#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);
}
template <typename T>
void do_forward(T v)

template <typename U>
void sink(U u)
int main()
{
    do_forward(s{});
}

Context

StackExchange Code Review Q#62485, answer score: 5

Revisions (0)

No revisions yet.