Why does for_each return function by move
Asked Answered
P

1

8

I was reading the documentation for std::for_each here http://en.cppreference.com/w/cpp/algorithm/for_each and saw that the return value is std::move(f)

Why does the standard enforce moving the input parameter in the return value? Won't it be moved by default anyway, since the input parameter is passed by value?


This leads me to a couple of followups, when you compile the following code

Something function(Something something) {
    return something;
} 
  1. The return statement is a move on my system with the highest optimization level (-O3), why don't most compilers elide this return value? Local values are elided but function arguments are not..

  2. Does C++17 enforce elision in this case? I read the proposal (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0135r0.html) but I do not fully understand which cases qualify for mandatory elision.

I have tried this on Apple LLVM version 8.0.0 (clang-800.0.42.1) on my Mac and on g++ 5.4 on Ubuntu 16.04.

Phenanthrene answered 2/4, 2017 at 1:46 Comment(0)
H
12

This is due to a late-breaking change in the move-semantics rules for C++11. The original move proposal did not automatically move when by-value function arguments appeared on the return clause. However by late in the C++11 process, that language feature was added.

Prior to the language feature being added, for_each "was moved". At that time the move on the return statement was necessary. But it became unnecessary, though harmless by the time C++11 shipped.

LWG issue 2747 corrected this for C++17.

As to your first followup question, I am not a compiler writer, but my best guess is: It is not currently legal to elide the return from a function parameter (that much I know), and I'm guessing as to why it isn't legal is that no one has figured out how to implement it, and thus no one has had the motivation to change the standard to make it legal.

Second follow up: No, C++17 does not enforce elision in this case. The rules remain the same as for C++11 in this case, save for the fact that the redundant move from for_each is no longer specified.

From the comments below:

Why do you say it's not legal to elide the return from a function parameter?

I'm referencing N4660, which is C++17, but there is similar wording in C++98/03/11/14 ... backup, it has recently been protected. See N4659 instead (just as good):

15.8.3 Copy/move elision [class.copy.elision]

  1. When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, ...

    • in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function parameter or a variable introduced by the exception-declaration of a handler (18.3)) with the same type (ignoring cv-qualification) as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function call’s return object

This language specifically disallows elision from function parameters.

Hire answered 2/4, 2017 at 2:12 Comment(9)
Why do you say it's not legal to elide the return from a function parameter? The way I look at it function parameter values are the same as local variables except that the order of construction and destruction is unspecified.Phenanthrene
@Curious: Updated with a quote from C++17 to address this.Hire
One last thing. Why did the proposal disallow elision from function arguments? I'm just trying to understand the reasoning behind the decision.Phenanthrene
@Curious: The original move proposal just said: Implicit move wherever elision is legal. That much I know, because I wrote it. As to why elision isn't legal from function parameters, as already stated in my answer I can only guess: no one has figured out how to implement it.Hire
Thanks for the detailed answer!Phenanthrene
@Phenanthrene you can't elide from a function argument because elision is enabled by the C calling convention: caller allocates both the arguments and return value, initializes the arguments, and cleans up both afterwards. Callee can choose to write directly to the return value's address/CPU register when it makes a local variable, but can't do what the caller didn't. It can't tell the caller to write the parameter into the would-be return value instead of writing it to the argument.Synchroflash
@Synchroflash Isn't the C calling convention dependent on the system's ABI?Phenanthrene
@Synchroflash I guess that itself along with what you mentioned can be a reason compiler implementors haven't been able to implement such a thing when the calling convention requires the caller to write the function argumentsPhenanthrene
@Phenanthrene The language makes it hard to invent a fundamentally different ABI. Actually I see a possibility for small objects though: in 64-bit Intel, passing an object whose only data members are two pointers (for example) by value uses rdi+rsi and returning it by value uses rax+rdx. If the compiler were allowed to elide this copy/move, it could just do two register-register movs.Synchroflash

© 2022 - 2024 — McMap. All rights reserved.