-
Notifications
You must be signed in to change notification settings - Fork 5.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Explain freedoms available in F.16-F.18 #1412
base: master
Are you sure you want to change the base?
Explain freedoms available in F.16-F.18 #1412
Conversation
As I discussed in isocpp#1407, I found the existing wording confusing as I expected that the guidelines would provide me with one recommended way to author function arguments in all situations. However, the discussion on the issue clarified that the intent was to allow many ways to author functions, and only to disallow specific clearly non-optimal cases. This is my attempt to reduce that confusion, by explicitly calling out when such freedoms exist.
Editors call: Thanks, we've reviewed this and we think we see where you're coming from. We believe that the guidance does actually provide a deterministic decision tree for how to write parameters. Note that each Item is about "what I want to do with the argument" -- e.g., "in" means "I want an X I can read from." So for example the last edit for F.18, which correctly notes that With that in mind, are there other changes that would make that clearer? |
Thanks for your time. I really appreciate the consideration and thought, as well as the rest of the work the editors do on this document. I completely see your point regarding my note in F.18 being inappropriate. I'll drop that part without further comment. However, unfortunately, I just can't agree with you that the guidelines as written provide a deterministic method for choosing a function's signature. As I mentioned in the discussion on the linked issue (#1407), many rules can apply to a given scenario, and the exact set of signature(s) you end up with depends deeply on which rule you choose to apply. struct A { vector<int> data; };
struct B { vector<int> data; };
// Follows F.16 - no optimization
B convert(const A& a) {
return B{a.data};
}
// Follows F.16 - with optimization
B convert(const A& a) {
return B{a.data};
}
B convert(A&& a) {
return B{std::move(a).data};
}
// Follows F.18
B convert(A&& a) {
return B{std::move(a).data};
}
// Follows F.19
template <typename A>
B convert(A&& a) {
return B{std::forward<A>(a).data};
} So, from the same starting point ("I want to convert A to B"), the guidelines can point me in four different directions regarding the type signatures in the overload set. To me, seeing the chart after F.15 made me think there would be a single recommended overload set for each scenario. This is what my edit was trying to clarify. I'm totally open to the idea that I'm missing something about how I'm supposed to think about this set of guidelines - if I am, please let me know! |
@@ -2847,9 +2849,9 @@ When copying is cheap, nothing beats the simplicity and safety of copying, and f | |||
|
|||
For advanced uses (only), where you really need to optimize for rvalues passed to "input-only" parameters: | |||
|
|||
* If the function is going to unconditionally move from the argument, take it by `&&`. See [F.18](#Rf-consume). | |||
* If the function is going to unconditionally move from the argument, take it by `&&`. See [F.18](#Rf-consume). You may also provide a `const &` overload that copies the argument rather than moves it, although this is not required. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is under the text For [...] where you really need to optimize for rvalues [...] I think this change is a little bit redundant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This came from a specific point I was confused by, which is that the guidelines allow you to provide only an &&
overload in these situations. To me, this is a useful clarification since it explicitly says that you're not required to provide a const &
overload as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on the shared_ptr
guidance in R.34 (and the discussion in #1989), should F.18 be updated to allow pass by value and then move?
// Follows F.18
B convert(A&& a) {
return B{std::move(a).data};
}
or
// Also follows F.18
B convert(A a) {
return B{std::move(a).data};
}
Pass by value is simpler than const&
and &&
overloads but does result in an extra move. I sometimes pass vectors this way in my own code. F.18 already has an exception for unique_ptr
, should another exception be added for cheap to move types?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those are two different cases: A&& forces a r- value to be passed (this is the classic "consume" semantic. A is a simplified version of the const A&
and A&&
overload set, which means I will keep a copy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I cant move from a const&, so this change seems wrong to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What this change meant to convey is - If you wrote a move-only sink function, it must use &&
. However, it may be more convenient for the caller if you provide both a &&
and const &
overload, and this is indeed also allowed by the guidelines. As written I've seen many people be confused by this note, since they thought since they wrote a function that always moves an argument, they are somehow not allowed to provide a const &
overload. They probably got this impression because they only read this note and did not perform a close reading of the full set of guidelines F.16-F.18
. That's the confusion I'm trying to avoid by editing this note. Could you suggest a better way to make this clarification?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess the first question is should the F.call guidelines be updated to allow pass by value and move if the callee consumes the parameter? Then if so, where should it go?
As I discussed in #1407, I found the existing wording confusing as I
expected that the guidelines would provide me with one recommended way
to author function arguments in all situations. However, the
discussion on the issue clarified that the intent was to allow many
ways to author functions, and only to disallow specific clearly
non-optimal cases.
This is my attempt to reduce that confusion, by explicitly calling out
when such freedoms exist.
As I mentioned also in #1407, I'm somewhat doubtful that providing
this much developer freedom is a good idea. A different way to solve this confusion
would have been to provide an explicit recommendation for these ambiguous cases.
However, I don't feel especially qualified to provide such a recommendation, hence
this PR.