Advice on std::forward
is generally limited to the canonical use case of perfectly forwarding function template arguments; some commentators go so far as to say this is the only valid use of std::forward
. But consider code like this:
// Temporarily holds a value of type T, which may be a reference or ordinary
// copyable/movable value.
template <typename T>
class ValueHolder {
public:
ValueHolder(T value)
: value_(std::forward<T>(value)) {
}
T Release() {
T result = std::forward<T>(value_);
return result;
}
private:
~ValueHolder() {}
T value_;
};
In this case, the issue of perfect forwarding does not arise: since this is a class template rather than a function template, client code must explicitly specify T
, and can choose whether and how to ref-qualify it. By the same token, the argument to std::forward
is not a "universal reference".
Nonetheless, std::forward
appears to be a good fit here: we can't just leave it out because it wouldn't work when T
is a move-only type, and we can't use std::move
because it wouldn't work when T
is an lvalue reference type. We could, of course, partially specialize ValueHolder
to use direct initialization for references and std::move
for values, but this seems like excessive complexity when std::forward
does the job. This also seems like a reasonable conceptual match for the meaning of std::forward
: we're trying to generically forward something that might or might not be a reference, only we're forwarding it to the caller of a function, rather than to a function we call ourselves.
Is this a sound use of std::forward
? Is there any reason to avoid it? If so, what's the preferred alternative?
This sort of wrapper works as long as it's used properly.
std::bind
does something similar. But this class also reflects that it is a one-shot functionality when the getter performs a move.ValueHolder
is a misnomer because it explicitly supports references as well, which are the opposite of values. The approach taken bystd::bind
is to ignore lvalue-ness and apply value semantics. To get a reference, the user appliesstd::ref
. Thus references are modeled by a uniform value-semantic interface. I've used a custom one-shotrref
class withbind
as well.There are a couple inconsistencies in the given implementation. The
return result;
will fail in an rvalue reference specialization because the nameresult
is an lvalue. You need anotherforward
there. Also aconst
-qualified version ofRelease
, which deletes itself and returns a copy of the stored value, would support the occasional corner case of a const-qualified yet dynamically-allocated object. (Yes, you candelete
aconst *
.)Also, beware that a bare reference member renders a class non-assignable.
std::reference_wrapper
works around this as well.As for use of
forward
per se, it follows its advertised purpose as long as it's passing some argument reference along according to the type deduced for the original source object. This takes additional footwork presumably in classes not shown. The user shouldn't be writing an explicit template argument… but in the end, it's subjective what is good, merely acceptable, or hackish, as long as there are no bugs.std::forward
is a conditionalmove
-cast (or more technically rvalue cast), nothing more, nothing less. While using it outside of a perfect forwarding context is confusing, it can do the right thing if the same condition on themove
applies.I would be tempted to use a different name, even if only a thin wrapper on
forward
, but I cannot think of a good one for the above case.Alternatively make the conditional move more explicit. Ie,
that is a noop pass-through if
bool
is false, andmove
if true. Then you can make your equivalent-to-std::forward
more explicit and less reliant on the user understanding the arcane secrets of C++11 to understand your code.Use:
On the third hand, the kind of thing you are doing above sort of requires reasonably deep understanding of rvalue and lvalue semantics, so maybe
forward
is harmless.