-
-
Notifications
You must be signed in to change notification settings - Fork 96
Passing rvalues to const ref args #111
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
Conversation
DIPs/DIP1xxx-rval_to_ref.md
Outdated
| There are many reasons why every function can't or shouldn't be a template. | ||
| 1. API is not your code, and it's not alrady `auto ref` | ||
| 2. Is distributed as a binary lib | ||
| 3. Is expored from DLL |
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.
typo: exported
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.
Did fix a couple of typos
|
Updated to clarify that only one scope should be introduced for the entire statement (clarifying behaviour for cascading calls), and that |
|
I could perhaps be persuaded to remove the word 'const' from this proposal... but I'd prefer to see that as a separate follow-up debate (relaxation of the spec is always easier than tightening). I don't really feel that symptoms of the restrictiveness of const are on trial here. |
|
Possibly add a bit about default parameters: extern(C++) void foo(ref const(int) = 0)
{
}Part of the problem when writing interfaces to C++ is that What gets called when? For the following: void foo(int);
void foo(const(int));
void foo(ref int);
void foo(ref const(int));Didn't see anything about this in there either, though I honestly don't know when the second definition would even be called? |
|
Nice points! |
DIPs/DIP1xxx-rval_to_ref.md
Outdated
| This issue is also likely to appear more frequently for vendors with tight ABI requirements. | ||
| Users of closed-source libraries distributed as binary libs, or libraries distributes as DLLs are more likely to encounter these challenges interacting with those APIs as well. | ||
|
|
||
| Another high-probability occurrence is OOP, where virtual function APIs inhibit the use of templates. |
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.
Another good use case is random generation. You always should take the generator by ref to avoid accidentally copying the generator (oops), but that makes the API more verbose as the user can't declare the RandomEngine and use it one line.
Phobos uses auto ref- it shouldn't :/
A summary of Phobos's broken std.random: https://dconf.org/2015/talks/wakeling.html
|
@TurkeyMan As per the new procedures, I'm setting this to DIP to Draft status. I'll also put out a call for Draft Review on the forums. |
|
I recommend adding a link to the ongoing forum discussion in the References section. |
|
"because it's a guarantee that the parameter is used strictly as an input argument, rather than some form of output." Consider a reference inside such a type as being a potentially sound place for an output. In other words, the struct itself is a reference to a temporary, but what its members point at may not be temporary. In C++ this is fine, because head-const does exist. But in D, requiring transitive const has a much more far-reaching effect. In any case, there is a large missing discussion in this DIP: the fact that |
|
Your proposal does not only break current code, but current expression, because someone might want to express take it by ref, but only lvalues, which as far as I see, cannot be done anymore. If so, it must be in the document. So, how about redefining The compiler can even suggest changing [1] https://forum.dlang.org/post/medovwjuykzpstnwbfyy@forum.dlang.org |
|
About the lowering, for |
|
Sorry for the silence guys. Work got very busy, and I haven't had any time after-hours for the last couple of weeks. |
My proposal is more semantically direct. The syntax you present introduces a function, which relies on the inliner to perform as expected to produce the proper calling code equivalent to my proposal. That will not happen in debug, and as such, the code may be hard to inspect (additional call in the callstack obscuring outer locals) and debug/step. |
|
About the lambda: I've seen stuff like that in other DIPs. I believe it's being used to specify behavior rigorously and rather simple and let the compiler guys decide on the implementation details. |
|
It's not right though; i made a point about introducing a single scope, not one for each call. Otherwise chains using return ref won't work as you expect. All temps need to live the life of the statement, not the call. (calls can be nested) |
67ea010 to
ba70f30
Compare
|
It took me some time to understand how your proposal changes how You should mention that with your proposal, void f(T) @disable; // catch and reject rvalues ...
void f(ref T); // ... therefore lvalues only
void g(ref T) @disable; // catch and reject lvalues ...
void g(T); // ... therefore rvalues onlyOne (good or bad depends on viewpoint) implication is that restriction to lvalues must be done much more explicit than before. Possibly there are algorithms that are not useful or don't even work correctly on l- or rvalues, but the assumption that those algorithms are rare certainly holds. So the proposal makes [1] jonskeet.uk or docs.microsoft.com (apart from ref locals) |
|
Right, I had that thought about retaining the restriction. If people want that, they can use @disable. |
|
Right, it's moved a little farther from C++ |
mdparker
left a comment
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.
As a first pass, I've made some suggestions for structural edits. I'll give you some time to address these before drilling down to a line/copy edit pass.
| | Author: | Manu Evans (turkeyman@gmail.com) | | ||
| | Status: | Draft | | ||
|
|
||
| ## Abstract |
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 think the abstract as written is too dense and too detailed. It can be made much more concise, likely stripped down to a single paragraph. For example, you don't need to iterate here any of the specific issues you detail in later in the Rationale. Just get across the main ideas: the current status quo is sometimes troublesome and this DIP proposes a means to alleviate the pain.
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.
Refactored
|
|
||
| Here is proposed a strategy to emit implicit temporaries to conveniently interact with APIs that use `ref` arguments. | ||
|
|
||
| ## Reference |
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.
It's great that you've listed so many references, but it's a bit much for anyone coming to all of this for the first time without anything to guide them. A one-line summary for each reference link (forum threads, issues, and PRs all) would be extremely helpful here.
DIPs/DIP1xxx-rval_to_ref.md
Outdated
|
|
||
| ## Rationale | ||
|
|
||
| When calling functions that receive `ref` args, D prohibits supplying rvalues. It is suggested that this is to assist the author identifying likely logic errors where an rvalue will expire at the end of the statement, and it doesn't make much sense for a function to mutate a temporary whose life will not extend beyond the function call in question. |
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 first sentence would work well for your opening statement in the abstract, though it could be reworded a bit, e.g. "D does not allow rvalues to be passed as function arguments where ref parameters are declared." or some such.
| With these cases in mind, the existing rule feels out-dated or inappropriate, and the presence of the rule may often lead to aggravation while trying to write simple, readable code. | ||
| Calling a function should be simple and orthogonal, generic code should not have to concern itself with details about ref-ness of function parameters, and users should not be required to jump through hoops when `ref` appears in API's they encounter. | ||
|
|
||
| Consider the example: |
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.
You examples should be collocated above with the issues they are intended to demonstrate, e.g.
"The first issue is yadda yadda.
Example Here
A related issue is yadda yadda.
Example here
DIPs/DIP1xxx-rval_to_ref.md
Outdated
| } | ||
| } | ||
| ``` | ||
| This example situation is simplified, but it is often that such issues appear in complex aggregate meta, which may be difficult to understand, or the issue is caused indirectly at some layer the user did not author. |
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.
Be careful about authoritative claims about frequency, or side effects that are hard to quantify. If you have precise claims to make, with demonstrative data to back them up, then the data need to be referenced somehow. Otherwise, consider language such as "can lead to" or "may cause", with examples to demonstrate. This applies not just here with these lines, but throughout the document.
DIPs/DIP1xxx-rval_to_ref.md
Outdated
|
|
||
| These work-arounds damage readability and brevity, they make authoring correct code more difficult, increase the probability of brittle meta, and it's frustrating to implement repeatedly. | ||
|
|
||
| ## Proposal |
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.
For consistency with other DIPs, let's keep the 'Description' label here.
DIPs/DIP1xxx-rval_to_ref.md
Outdated
|
|
||
| It is important that `T` be defined as the argument type, and not `auto`, because it will allow implicit conversions to occur naturally, with identical behaviour as when argument was not `ref`. The user should not experience edge cases, or differences in functionality when calling `fun(int x)` vs `fun(ref int x)`. | ||
|
|
||
| ## Temporary destruction |
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 should be a level 3 subheading under description, as should all the headings between here and the copyright.
DIPs/DIP1xxx-rval_to_ref.md
Outdated
| When calling functions that receive `ref` args, D prohibits supplying rvalues. It is suggested that this is to assist the author identifying likely logic errors where an rvalue will expire at the end of the statement, and it doesn't make much sense for a function to mutate a temporary whose life will not extend beyond the function call in question. | ||
|
|
||
| However, many functions receive arguments by reference, and this may be for a variety of reasons. | ||
| One common reason is that the cost of copying large structs via the parameter list is expensive, so struct parameters may be received by reference to mitigate this cost. |
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'd change this to "the cost of copying or moving large structs".
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.
👍
DIPs/DIP1xxx-rval_to_ref.md
Outdated
| One common reason is that the cost of copying large structs via the parameter list is expensive, so struct parameters may be received by reference to mitigate this cost. | ||
| Another common case is that the function may want to mutate the caller's data directly or return data via `out` parameters due to ABI limitations regarding multiple return values. This is the potential error case that the existing design attempts to mitigate, but in D, pipeline programming is vary popular, and contrary to conventional wisdom where the statement is likely to end at the end of the function call, pipeline expressions may result in single statements performing a lot of work, mutating state as it passes down the pipeline. | ||
|
|
||
| A related issue is with relation to generic code which reflects or received a function by alias. Such generic code may want to call that function, but it is often the case that details about the ref-ness of arguments lead to incorrect semantic expressions in the generic code depending on the arguments, necessitating additional compile-time logic to identify the ref-ness of function arguments and implement appropriate workarounds on these conditions. This leads to longer, more brittle, and less-maintainable generic code. It is also much harder to write correctly the first time, and such issues may only emerge in niche use cases at a later time. |
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.
An example here would help.
DIPs/DIP1xxx-rval_to_ref.md
Outdated
| fun(my_short); // implicit type conversions (ie, short->int promotion) | ||
| // etc... (basically, most things you pass to functions) | ||
| ``` | ||
| The work-around can bloat the number of lines around the call-site significantly, and the user needs to declare names for all the temporaries, polluting the local namespace, and often for expressions where no meaningful name exists, leading to. |
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.
The "leading to" at the end of the sentence doesn't seem to belong there.
| Is rewritten: | ||
| ```d | ||
| { | ||
| T __temp0 = void; |
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.
Why not T __temp0 = 10?
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.
Because that would change the order of evaluation.
The order of evaluation should match function call argument evaluation exactly. I tried to express that in the simplest terms I could.
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.
Another point is: T might not be constructable from an int.
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.
In the text below: "It is important that T be defined as the argument type", so in this case, T is int.
Removed `const` from proposal.
d91d6af to
f310689
Compare
Copy edit pass.
|
Integrate changes and tweaked a touch. |
Created a DIP for the rval->ref situation