I have the following situation where I need to move construct t2 from t1.
Unfortunately it is not possible to do that (constness violation I suppose)
What is the right approach to handle that transparently from the caller of foo ? (ie. without requiring a passing by value and an explicit std::move)
struct T
{
T() = default;
~T() = default;
T(T&&) = default;
};
T foo(const T& t)
{
T t3;
if (predicate)
return t3;
else
return std::move(t);
}
int main()
{
T t1;
T t2 = foo(t1);
return 0;
}
First of all, you cannot move from an object that is marked as const . Another potential problem is when a compiler is trying to use (Named) Return Value Optimization (NRVO or RVO).
To correct this, remove the move constructor completely. In the case of the class, once a copy constructor is present (user defined), the move is implicitly not generated anyway (move constructor and move assignment operator).
If a copy constructor, copy-assignment operator, move constructor, move-assignment operator, or destructor is explicitly declared, then: No move constructor is automatically generated. No move-assignment operator is automatically generated.
What is the right approach to handle that transparently from the caller of foo ?
You cannot do that, and that's intended to be so. An lvalue is never moved from transparently.
Since a moved-from object is usually left in an unknown (yet legal) state, it would be dangerous if a client could pass an lvalue as the argument to a function, and that function were allowed to silently move from it. The client may be left with a zombie object without even knowing it!
Therefore, the language enforces the rule that moving from an lvalue must be a conscious action, and shall occur by explicitly turning the lvalue into an rvalue (actually, an xvalue) through a call to std::move()
.
In this case, I would say your function foo()
- whatever it is actually supposed to do - should take an rvalue reference, and the client should invoke it like so:
T t2 = foo(std::move(t1));
Notice, that this last suggestion may not be correct once you turn a meaningless name such as foo()
into something that reflects the semantics of some particular operation in some concrete program. Without knowing what is the meaning of that operation, however, I can only provide formal, mechanical advice on how to make your code snippet compile, and how to think about move semantics in general.
You could do this if you wanted to badly enough. To do it, your foo
would end up as essentially a re-implementation of std::move
.
struct T
{
T() = default;
~T() = default;
T(T&&) = default;
};
template<class T> struct my_remove_reference {typedef T type;};
template<class T> struct my_remove_reference<T&> {typedef T type;};
// The third part of a normal `remove_reference`, but which we don't need in
// this case (though it'll all still be fine if you un-comment this).
//template<class T> struct my_remove_reference<T&&> {typedef T type;};
template <typename T>
typename my_remove_reference<T>::type &&foo(T &&t) {
return static_cast<typename my_remove_reference<T>::type &&>(t);
}
int main()
{
T t1;
T t2 = foo(t1);
return 0;
}
Now, despite the fact that you can/could do this, the other answers remain more or less correct -- even though you can do it, you almost certainly shouldn't. If you used this foo
on a real class that supports move construction (i.e., will really move the state from the source to the destination) you'd end up basically destroying your source object even though it remains visible.
Doing this with your edited question, where foo
could, but wouldn't always, destroy the source object would be particularly pernicious. With the right name that makes it clear that foo
really moves from the source, it could, possibly, be just barely reasonable to do this -- but (at least to me) a function that might or might not destroy its argument seems drastically worse than one that does so dependably. Obviously, a version of foo
like above that just does the same thing as std::move
is pointless, but a version that also did some other processing on the input (and, for whatever reason, couldn't be used along with std::move
) might, possibly, be just barely acceptable if you really didn't have any other choice.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With