I love a good ‘quadrant’ diagram. It brings me immense joy if I can encapsulate some wisdom, guideline or rule-of-thumb in a simple four-quadrant picture.
This time it’s the when-and-where of std::move and std::forward. In my experience, when programmers are first introduced to move semantics, their biggest struggle is to know when (or when not) to apply std::move or std::forward. Usually, it’s a case of “keep apply std::move until it compiles”. I’ve been there myself.
To that end I’ve put together a couple of a simple overview quadrant graphics to help out the neophyte ‘mover-forwarder’. The aim is to capture some simple rules-of-thumb in an easy-to-digest format.
Disclaimer: these diagrams don’t address every move/forwarding use. They’re not intended to. That’s why we have books, presentations and long rambling articles on the topic.
You’re writing a function…
You want to know whether you should use std::move or std::forward on the parameter(s)
Example 1: A non-template function with an l-value reference parameter.
You don’t want to use std::move or std::forward because the client should expect their object to be modified – but not expired – after the call.
void func(ADT& param) { ADT temp = param; // Copy object }
Example 2: A template function with an l-value reference parameter
As above, but this time the type of the parameter is (probably) deduced.
template <typename T> void func(T& param) { T temp = param; // Copy object }
Example 3: A non-template function with an r-value reference parameter
Overloading with an r-value reference parameter is an explicit declaration that you intend to move from the parameter.
void func(ADT&& param) { ADT temp = std::move(param); // See rules below }
Example 4: A template function with an r-value reference parameter
This is the Forwarding Reference Idiom. Your function is forwarding-on its parameter to some underlying function. Typically, you want to pass that argument on without changing its value category.
template <typename T> void wrapper_func(T&& param) { utility_func(std::forward<T>(param)); // l-values are copied // r-values are moved }
You’re calling a function…
You want to know whether you should use std::move or std::forward on the argument. By the way, you can apply this set of rules to returning parameters from functions, too.
Example 1: The expression is an l-value; you need the object after the call.
In other words, a typical function call!
int main() { ADT adt { }; func(adt); // use adt again... }
Example 2: The expression is an r-value.
The object will be destroyed after the call, so you can’t retain it. There is no need to call std::move on an object that is already an r-value expression.
int main() { func(ADT { }); // This object is just for the call }
Example 3: The expression is an l-value; you don’t need the object after the call.
The object will be an x-value (expired) object after the call.
int main() { ADT adt { }; func(std::move(adt)); // adt shouldn’t be used again... }
Want to know more?…
If you want to explore this in more detail have a read here, here, here and even here
- Practice makes perfect, part 3 – Idiomatic kata - February 27, 2020
- Practice makes perfect, part 2– foundation kata - February 13, 2020
- Practice makes perfect, part 1 – Code kata - January 30, 2020
Glennan is an embedded systems and software engineer with over 20 years experience, mostly in high-integrity systems for the defence and aerospace industry.
He specialises in C++, UML, software modelling, Systems Engineering and process development.
A function that has an rvalue-ref parameter isn't a declaration that it will be moved from, only that it may be moved from. (Probably different if it's in an overload set.)
I suppose yours is one of the best C++ sites till now...it's in top 10 for sure...