bugprone-move-forwarding-reference¶
Warns if std::move is called on a forwarding reference, for example:
template <typename T> void foo(T&& t) { bar(std::move(t)); }
Forwarding references should
typically be passed to std::forward instead of std::move, and this is
the fix that will be suggested.
(A forwarding reference is an rvalue reference of a type that is a deduced function template argument.)
In this example, the suggested fix would be
bar(std::forward<T>(t));
Background¶
Code like the example above is sometimes written with the expectation that
T&& will always end up being an rvalue reference, no matter what type is
deduced for T, and that it is therefore not possible to pass an lvalue to
foo(). However, this is not true. Consider this example:
std::string s = "Hello, world"; foo(s);
This code compiles and, after the call to foo(), s is left in an
indeterminate state because it has been moved from. This may be surprising to
the caller of foo() because no std::move was used when calling
foo().
The reason for this behavior lies in the special rule for template argument
deduction on function templates like foo() – i.e. on function templates
that take an rvalue reference argument of a type that is a deduced function
template argument. (See section [temp.deduct.call]/3 in the C++11 standard.)
If foo() is called on an lvalue (as in the example above), then T is
deduced to be an lvalue reference. In the example, T is deduced to be
std::string &. The type of the argument t therefore becomes
std::string& &&; by the reference collapsing rules, this collapses to
std::string&.
This means that the foo(s) call passes s as an lvalue reference, and
foo() ends up moving s and thereby placing it into an indeterminate
state.