The first of the remaining two rules for rvalue references affects old-style
lvalue references as well. Recall that in pre-11 C++, it was not allowed to
take a reference to a reference: something like A& & would cause
a compile error. C++11, by contrast, introduces the following reference
collapsing rules1:
template<typename T> void foo(T&&);Here, the following apply:
template<typename T, typename Arg> shared_ptr<T> factory(Arg&& arg) { return shared_ptr<T>(new T(std::forward<Arg>(arg))); }where std::forward is defined as follows:
template<class S> S&& forward(typename remove_reference<S>::type& a) noexcept { return static_cast<S&&>(a); }(Don't pay attention to the noexcept
keyword for now. It lets the compiler know, for certain optimization purposes, that this function will never throw an exception.
We'll come back to it in Section 9.) To see how the code above achieves perfect forwarding, we will
discuss separately what happens when our factory function gets called on lvalues and rvalues. Let A and
X be types. Suppose first that factory<A> is called
on an lvalue of type X :
X x; factory<A>(x);Then, by the special template deduction rule stated above, factory 's template
argument Arg resolves to X& . Therefore, the compiler will create
the following instantiations of factory and std::forward :
shared_ptr<A> factory(X& && arg) { return shared_ptr<A>(new A(std::forward<X&>(arg))); } X& && forward(remove_reference<X&>::type& a) noexcept { return static_cast<X& &&>(a); }After evaluating the remove_reference and applying the reference collapsing rules, this becomes:
shared_ptr<A> factory(X& arg) { return shared_ptr<A>(new A(std::forward<X&>(arg))); } X& std::forward(X& a) { return static_cast<X&>(a); }This is certainly perfect forwarding for lvalues: the argument arg of the factory
function gets passed on to A 's constructor through two levels of indirection,
both by old-fashioned lvalue reference.
Next, suppose that X foo(); factory<A>(foo());Then, again by the special template deduction rule stated above, factory 's template
argument Arg resolves to X . Therefore, the compiler will now create
the following function template instantiations:
shared_ptr<A> factory(X&& arg) { return shared_ptr<A>(new A(std::forward<X>(arg))); } X&& forward(X& a) noexcept { return static_cast<X&&>(a); }This is indeed perfect forwarding for rvalues: the argument of the factory function gets passed on to A 's constructor through two levels of indirection,
both by reference. Moreover, A 's constructor sees as its argument an
expression that is declared as an rvalue reference and does not have a name. By the
no-name rule, such a thing is an rvalue. Therefore,
A 's constructor gets called on an rvalue. This means that the forwarding
preserves any move semantics that would have taken place if the factory wrapper were
not present.
It is perhaps worth noting that the preservation of move semantics is in fact the only
purpose of
If you want to dig a little deeper for extra credit, ask yourself this question: why
is the
Rejoice. We're almost done. It only remains to look at the implementation of template<class T> typename remove_reference<T>::type&& std::move(T&& a) noexcept { typedef typename remove_reference<T>::type&& RvalRef; return static_cast<RvalRef>(a); }Suppose that we call std::move on an lvalue of type X :
X x; std::move(x);By the new special template deduction rule, the template argument T will resolve to X& . Therefore,
what the compiler ends up instantiating is
typename remove_reference<X&>::type&& std::move(X& && a) noexcept { typedef typename remove_reference<X&>::type&& RvalRef; return static_cast<RvalRef>(a); }After evaluating the remove_reference and applying the new reference collapsing rules,
this becomes
X&& std::move(X& a) noexcept { return static_cast<X&&>(a); }That does the job: our lvalue x will bind to the lvalue reference that is the argument
type, and the function passes it right through, turning it into an unnamed rvalue reference.
I leave it to you to convince yourself that std::move(x);you could just as well write static_cast<X&&>(x);However, std::move is strongly preferred because it is more expressive.
1The reference collapsing rules stated here are correct, but
not entirely complete. I have omitted a small detail that is not relevant in our context, namely, the disappearance of
const and volatile qualifiers during reference collapsing. Scott Meyer's "Effective Modern C++"
modification history and errata page
has the full explanation.
|