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 sink
If 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:
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
).
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 sink
If 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:
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
).
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 sink
If 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:
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
).
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 sink
If 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:
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
).