Reputation: 1330
In the code below (which is run in C++20), when I call the UseForward
function, I expect the first overload to be called (which is template <typename T> void UseForward(T& value)
) and it does get called. Then in the body of the function I use std::forward
which I expect to preserve the lvalue-ness of variable value
and call the copy constructor, BUT it calls the move constructor. What am I missing here?
class SomeClass
{
public:
SomeClass()
{
std::cout << "default constructor" << std::endl;
}
SomeClass(const SomeClass& other)
{
std::cout << "const copy constructor" << std::endl;
}
SomeClass(SomeClass&& other) noexcept
{
std::cout << "move constructor" << std::endl;
}
};
template <typename T>
void UseForward(T& value)
{
std::cout << "UseForward pass by reference!" << std::endl;
auto sc = SomeClass(std::forward<T>(value));
}
template <typename T>
void UseForward(T&& value)
{
std::cout << "UseForward pass by rvalue reference!" << std::endl;
auto sc2 = SomeClass(std::forward<T>(value));
}
int main()
{
auto sc = SomeClass();
UseForward(sc);
}
Upvotes: 2
Views: 162
Reputation: 14688
Comment out first definition of UseForward
and behold, both
UseForward(sc);
UseForward(SomeClass());
would say that you had passed value by rvalue reference. That's because && with cv-unqualified type T (T&&
) in substitution context is NOT an rvalue reference but so-called forwarding reference, it preserves a value cathegory.
For desired effect you have to use type traits, by SFINAE or by compile-time alternatives:
template <typename T>
void UseForward(T&& value)
{
if constexpr (std::is_rvalue_reference_v<decltype(value)>)
std::cout << "UseForward pass by rvalue reference!" << std::endl;
else
std::cout << "UseForward pass by reference!" << std::endl;
auto sc2 = SomeClass(std::forward<T>(value));
}
Upvotes: 4
Reputation: 39869
As the other answers have already explained, you're misusing std::forward
inside UseForward(T&)
, and it effectively behaves like std::move
. Removing that function ends up calling UseForward(T&&)
, which accepts a forwarding reference (not an rvalue reference), and std::forward
works correctly.
std::forward
is expected to be usedIt's also worth noting why that actually happens, and why std::forward
is designed like that. std::forward
expects either:
std::forward<T>(t)
where t
is a forwarding reference, orstd::forward<decltype(t)>(t)
where t
is any expressionstd::forward
interacts with forwarding referencesInside UseForward(T&&)
, when called with an lvalue of type SomeClass
, T
will deduce to SomeClass&
. Due to reference collapsing, a T&&
where T = SomeClass&
(i.e. rvalue ref. + lvalue ref.), collapses to SomeClass&
. This means you are calling std::forward<SomeClass&>(SomeClass&)
, which results in an lvalue.
However, in UseForward(T&)
, T
will simply deduce to SomeClass
, and you are calling std::forward<SomeClass>(SomeClass&)
. In such a case, std::forward
will result in an xvalue, and you end up calling the move constructor.
const
-correctness can save you troubleOn a side note, you wouldn't have accidentally called the move constructor if your overloads consisted of:
UseForward(const T&)
andUseForward(T&&)
Even if you had misused std::forward
inside the former function, the const
would have prevented you from calling the move constructor. In general, you almost never want to overload between mutable lvalue/rvalue references, but rather between const lvalue references, and mutable rvalue references.
Never use std::forward
unless working with forwarding references. Instead:
std::move
std::forward
for forwarding references as intendedtemplate <typename T>
void UseForward(T&& value)
{
std::cout << "UseForward pass by forwarding reference!" << std::endl;
auto sc2 = SomeClass(std::forward<T>(value));
}
Upvotes: 1
Reputation: 1200
The answer is: you're asking it to.
std::forward<T>(v)
where decltype (v)
is T&
.
See: std::forward
template <typename T>
void UseForward(T& value) {
std::cout << "UseForward pass by reference!" << std::endl;
auto sc = SomeClass(std::forward<decltype (value)>(value));
}
Upvotes: 3