Back to Basics: Move Semantics Part 2

Back to Basics: Move Semantics Part 2

Tags
c++
Published
March 11, 2020
Author
Chris Chan
 
 
Video preview
 

Forwarding References

A forwarding reference is like a shapeshifter. It is an lvalue if initialized by an lvalue and rvalue if initialized by an rvalue. They typically appear in the form of T&& or auto&& and are used when type deduction is involved.
template <typename T> void f(T&& x); auto&& var2 = var1;

Reference Collapsing

The rules below are used by the compiler to collapse lvalue and rvalue references adjacent to one another. & & -> &&& & -> && && -> &&& && -> &&

Perfect forwarding

std::forward will considtionally cast its input into an rvalue reference. Given an lvalue, it will cast the input to lvalue. Given an rvalue, it will cast the input to an rvalue
template <typename T> T&& forward(std::remove_reference_t<T>& t) noexcept { return static_cast<T&&>(t); }
Suppose I pass an lvalue, so we replace T with SomeClass&. By reference collapsing SomeClass& && become SomeClass&. Remove reference will strip lvalue or value references from the SomeClass type so std::remove_reference_t<SomeClass&>& t evaluates to SomeClass& t. The resulting forward function instantiated is.
template < > SomeClass& forward(SomeClass& t) noexcept { return static_cast<SomeClass&>(t); }
Suppose I pass an rvalue, so we replace T with SomeClass&&. Similar to the logic above, SomeClass&& && become SomeClass&& and std::remove_reference_t<SomeClass&&>& t evaluates to SomeClass& t.The resulting forward function instantiated is.
template < > SomeClass&& forward(SomeClass& t) noexcept { return static_cast<SomeClass&&>(t); }

Perfect Forwarding

With the help of perfect forwarding we can forward the type of an argument into a function into another function being called. To illustrate the power of perfect forwarding we use the example of the make_unique function. A naive approach might be similar to the ones below.
// the argument `arg` is always copied into function and passed as lvalue to T template<typename T, typename Arg> unique_ptr<T> make_unique(Arg arg) { return unique_ptr<T>(new T(arg) ); } // `arg` is passed as lvalue to T, but what if I want an rvalue to be passed to T :( template<typename T, typename Arg> unique_ptr<T> make_unique(Arg&& arg) { return unique_ptr<T>(new T(arg) ); }
With std::forward we can forward the type of args into T's constructor. So T can now be initialized using an rvalue or lvalue without the need for expensive copies.
template<typename T, typename... Args> unique_ptr<T> make_unique(Args&&... args) { return unique_ptr<T>(new T(std::forward<Args>(args)... ) ); }

Move

The machanics of std::move is very similar to std::foward in that it uses std::remove_reference_t to gernerate the correct reference type.
template <typename T> std::remove_reference_t<T>&& move(T&& t) noexcept { return static_cast<std::remove_reference_t<T>&&>(t); }