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?
The std::forward function as the std::move function aims at implementing move semantics in C++. The function takes a forwarding reference. According to the T template parameter, std::forward identifies whether an lvalue or an rvalue reference has been passed to it and returns a corresponding kind of reference.
std::move takes an object and casts it as an rvalue reference, which indicates that resources can be "stolen" from this object. std::forward has a single use-case: to cast a templated function parameter of type forwarding reference ( T&& ) to the value category ( lvalue or rvalue ) the caller used to pass it.
Summary. In a type declaration, “ && ” indicates either an rvalue reference or a universal reference – a reference that may resolve to either an lvalue reference or an rvalue reference. Universal references always have the form T&& for some deduced type T .
When t is a forwarding reference (a function argument that is declared as an rvalue reference to a cv-unqualified function template parameter), this overload forwards the argument to another function with the value category it had when passed to the calling function.
std::forward
is a conditional move
-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 the move
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,
template<bool do_move, typename T>
struct helper {
auto operator()(T&& t) const
-> decltype(std::move(t))
{
return (std::move(t));
}
};
template<typename T>
struct helper<false, T> {
T& operator()(T&& t) const { return t; }
};
template<bool do_move, typename T>
auto conditional_move(T&& t)
->decltype( helper<do_move,T>()(std::forward<T>(t)) )
{
return ( helper<do_move,T>()(std::forward<T>(t)) );
}
that is a noop pass-through if bool
is false, and move
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:
std::unique_ptr<int> foo;
std::unique_ptr<int> bar = conditional_move<true>(foo); // compiles
std::unique_ptr<int> baz = conditional_move<false>(foo); // does not compile
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.
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 by std::bind
is to ignore lvalue-ness and apply value semantics. To get a reference, the user applies std::ref
. Thus references are modeled by a uniform value-semantic interface. I've used a custom one-shot rref
class with bind
as well.
There are a couple inconsistencies in the given implementation. The return result;
will fail in an rvalue reference specialization because the name result
is an lvalue. You need another forward
there. Also a const
-qualified version of Release
, 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 can delete
a const *
.)
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With