You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[BUG] Suspicious cpp1 std::move of returning expression #313
I'm not C++ expert but I believe std::move(x) >> 1 doesn't make much sense in cpp1. It compiles, but I think move is lost to nowhere. (May be I am wrong and x is still marked as to be moved? But I think not)
To Reproduce
I have created this test:
dup: (a:_) -> _ = {
x := a;
return x + x;
}
inc: (a:_) -> _ = {
x := a;
return x + 1;
}
main: () -> int = {
obj := 42;
std::cout << dup(obj) << '\n';
std::cout << inc(obj);
}
which generates following cpp1:
[[nodiscard]] auto dup(auto const& a) -> auto{
auto x {a};
return x + std::move(x);
}
[[nodiscard]] auto inc(auto const& a) -> auto{
auto x {a};
return std::move(x) + 1;
}
I believe both of those cases loses information about move.
Additional context
Example uses int, but I have checked with custom type (with operator+) and it behaves the same.
Also its the same for other operators like *, >> etc.
This is bad, as the move could happen before the copy.
7 #
The postfix-expression is sequenced before each expression in the expression-list and any default argument.
The initialization of a parameter, including every associated value computation and side effect, is indeterminately sequenced with respect to that of any other parameter.
So if std::move is sort of cast to &&, does it boils down to operator+ having (&&) overload? What if custom class doesn't have it? Is then result of + operation needed to be marked with std::move again? Sorry may be I am overthinking that, and examples on std::move usually work with just one value, not operations on them. Anyway I hope spotting x + std::move(x) still helpful here.
For parameters yes, but I got impression that std::move on return value was needed to enable some compiler optimization on result object, in that case on result of operator+
Thanks for the note. Generally the status quo is as-intended, the move from last use should be innocuous if the target doesn't make use of the rvalue-ness.
This is bad, as the move could happen before the copy.
Ah yes, we didn't get l-to-r OOEE for function arguments. I should suppress move from last use when there is another use of the variable in the same expression.
Ah yes, we didn't get l-to-r OOEE for function arguments. I should suppress move from last use when there is another use of the variable in the same expression.
According to
[Note 8: If an operator function is invoked using operator notation, argument evaluation is sequenced as specified for the built-in operator; see [over.match.oper].
— end note]
Right, I should have said in the same top-level function-call-expression... not necessarily just in the same parameter list because there could be nested function call expressions, like f(x, g(x)). And as your example points out, the function call expression might be using infix operator notation instead of an explicit function call with a list, so probably the best thing to do is to suppress move-from-last-use when there is another use in the same statement.
Overloadable. Is a specified order of evaluation a good measure to move-from-last-use? Definitely suspicious! (And so is the reappearance of x itself).
If you want to move from a returned lvalue reference, that should be explicit.
I'm not sure how to express that in pure cpp2.
//foo: () -> (move result : int) = { //error: a return value cannot be 'move' (it is implicitly 'move'-out)
foo: () -> int = { lambda := :()->_ = 42; //return move lambda(); //error //return std::move(lambda()); //error: std::move is not needed in Cpp2 - use 'move' parameters/arguments instead }
I have tried also with out parameter, but that compicates API at caller site (have to introduce variable), and still generates cpp1 where lambda moved but not result: result.construct(std::move(lambda)());
Sorry if I'm overcomplicating or missing something.
Activity
JohelEGP commentedon Apr 2, 2023
This is bad, as the move could happen before the copy.
realgdman commentedon Apr 2, 2023
For the sake of completeness, I have though about another example:
which gives
Once again I don't know if that's ok. Should it be
std::move(x+y)ormove(move(x) + move(y))or is that fine now.JohelEGP commentedon Apr 2, 2023
It's fine as is. I'd suggest checking out https://www.youtube.com/watch?v=knEaMpytRMA&list=PLHTh1InhhwT6c2JNtUiJkaH8YRqzhU7Ag&index=113.
realgdman commentedon Apr 2, 2023
So if std::move is sort of cast to &&, does it boils down to operator+ having (&&) overload? What if custom class doesn't have it? Is then result of + operation needed to be marked with std::move again? Sorry may be I am overthinking that, and examples on std::move usually work with just one value, not operations on them. Anyway I hope spotting x + std::move(x) still helpful here.
JohelEGP commentedon Apr 2, 2023
operator+'s parameters are usuallyconst&, and you can initialize those with rvalues.realgdman commentedon Apr 2, 2023
For parameters yes, but I got impression that std::move on return value was needed to enable some compiler optimization on result object, in that case on result of operator+
hsutter commentedon Apr 8, 2023
Thanks for the note. Generally the status quo is as-intended, the
movefrom last use should be innocuous if the target doesn't make use of the rvalue-ness.However, @JohelEGP correctly points out:
Ah yes, we didn't get l-to-r OOEE for function arguments. I should suppress move from last use when there is another use of the variable in the same expression.
JohelEGP commentedon Apr 8, 2023
According to
there are some exceptions.
hsutter commentedon Apr 9, 2023
Right, I should have said in the same top-level function-call-expression... not necessarily just in the same parameter list because there could be nested function call expressions, like
f(x, g(x)). And as your example points out, the function call expression might be using infix operator notation instead of an explicit function call with a list, so probably the best thing to do is to suppress move-from-last-use when there is another use in the same statement.JohelEGP commentedon Apr 10, 2023
I meant that the order of evaluation of these are specified:
x ? std::move(x) : std::move(x).x && std::move(x)x || std::move(x)x, std::move(x)xitself).x[std::move(x)]x(std::move(x))x = std::move(x)Note that
xisn't necessarily used directly. It could bef(y)andg(y)for the left and right hand sides, respectively.realgdman commentedon Apr 12, 2023
I've noticed that
foo: () -> int = {lambda := :()->_ = 42;return lambda();}generates following cpp1:
[[nodiscard]] auto foo() -> int{auto lambda {[]() -> auto { return 42; }};return std::move(lambda)();}Should it be
return std::move( lambda() );instead?I suppose we want to move returned value from it, not lambda itself?
JohelEGP commentedon Apr 12, 2023
No. Check #313 (comment) again. If you want to move from a returned lvalue reference, that should be explicit.
realgdman commentedon Apr 12, 2023
Sorry, it still hard to wrap my head around.
//foo: () -> (move result : int) = {//error: a return value cannot be 'move' (it is implicitly 'move'-out)foo: () -> int = {lambda := :()->_ = 42;//return move lambda(); //error//return std::move(lambda()); //error: std::move is not needed in Cpp2 - use 'move' parameters/arguments instead}I have tried also with out parameter, but that compicates API at caller site (have to introduce variable), and still generates cpp1 where lambda moved but not result:
result.construct(std::move(lambda)());Sorry if I'm overcomplicating or missing something.
filipsajdak commentedon Apr 12, 2023
When a function returns value it is implicitly return with
movepassing style. You can change it toforwardin some cases.I don't have my PC near me. Does below code works? I am not sure.
realgdman commentedon Apr 12, 2023
Nope, that's
error: missing ; after return (at 'lambda')JohelEGP commentedon Apr 12, 2023
This works to move from a returned lvalue reference: https://godbolt.org/z/r1oPfY89j.
filipsajdak commentedon Apr 12, 2023
Ah, yes. Parentheses are needed.
Right?
realgdman commentedon Apr 12, 2023
Oh right, don't know why I haven't tried that. It generates
return (std::move(std::move(lambda)()));filipsajdak commentedon Apr 12, 2023
This is the last use of lambda so it will be moved.