Question: is this the recommended way to apply perfect forwarding for multiple passes over the same data?
Yes, this is the recommended way to apply perfect forwarding (or move) when you need to pass the data multiple times. Only (potentially) move from it on your last access. Indeed, this scenario was foreseen in the original move paper, and is the very reason that "named" variables declared with type rvalue-reference are not implicitly moved from. From N1377:
Even though named rvalue references can bind to an rvalue, they are
treated as lvalues when used. For example:
struct A {};
void h(const A&);
void h(A&&);
void g(const A&);
void g(A&&);
void f(A&& a)
{
g(a); // calls g(const A&)
h(a); // calls h(const A&)
}
Although an rvalue can bind to the "a" parameter of f(), once bound, a
is now treated as an lvalue. In particular, calls to the overloaded
functions g() and h() resolve to the const A& (lvalue) overloads.
Treating "a" as an rvalue within f would lead to error prone code:
First the "move version" of g() would be called, which would likely
pilfer "a", and then the pilfered "a" would be sent to the move
overload of h().
If you want h(a)
to move in the above example, you have to do so explicitly:
h(std::move(a)); // calls h(A&&);
As Casey points out in the comments, you have an overloading problem when passing in lvalues:
#include <utility>
#include <type_traits>
template<class T>
class parse
{
static_assert(!std::is_lvalue_reference<T>::value,
"parse: T can not be an lvalue-reference type");
public:
// parse will modify a local copy or move of its input parameter
void operator()(T const& arg, int n) const { /* optimized for lvalues */ }
void operator()(T&& arg , int n) const { /* optimized for rvalues */ }
};
template<class T>
void split(T&& arg, int n)
{
typedef typename std::decay<T>::type Td;
for (auto i = 0; i < n - 1; ++i)
parse<Td>()(arg , i); // copy n-1 times
parse<Td>()(std::forward<T>(arg), n - 1); // possibly move the n-th time
}
Above I've fixed it as Casey suggests, by instantiating parse<T>
only on non-reference types using std::decay
. I've also added a static_assert to ensure that the client does not accidentally make this mistake. The static_assert
isn't strictly necessary because you will get a compile-time error regardless. However the static_assert
can offer a more readable error message.
That is not the only way to fix the problem though. Another way, which would allow the client to instantiate parse
with an lvalue reference type, is to partially specialize parse:
template<class T>
class parse<T&>
{
public:
// parse will modify a local copy or move of its input parameter
void operator()(T const& arg, int n) const { /* optimized for lvalues */ }
};
Now the client doesn't need to do the decay
dance:
template<class T>
void split(T&& arg, int n)
{
for (auto i = 0; i < n - 1; ++i)
parse<T>()(arg , i); // copy n-1 times
parse<T>()(std::forward<T>(arg), n - 1); // possibly move the n-th time
}
And you can apply special logic under parse<T&>
if necessary.
T
for, and doesparse
consume it? Can you for examplemove()
the instance back on return fromparse()
? (my point being, ifparse()
consumes it, then you can only forward to it once, else, forward and move back, or better yet, always deal with the const reference...) – Stereophonicparse()
will modifyarg
and then discard the result, maybe writing some output or return an integer value, but notarg
itself. – Indulgencearg
which is passed via const reference? – StereophonicT&&
overload, I can modify onstd::move(arg)
rather than on a local copy. – IndulgenceT
is, you could for example extract out the state you modify and see if you can make copying of this cheap, but it's hard to say... – StereophonicT
is astd::array<uint64_t, 2>
, so no optimized move constructor, but theparse()
function is generic so I want to be able to pass say astd::vector<uint64_t>
as well. – Indulgenceconst
lvalue reference tosplit()
, thenT
will deduce to an lvalue reference type and the compiler will refuse to overload bothoperator()(T const&)
andoperator()(T&)
sinceT&&
collapses to&
when T is an lvalue reference (Example at Coliru). I think you wantparse<typename std::decay<T>::type>
instead ofparse<T>
. – Stitch