DIP 1016--ref T accepts r-values--Formal Assessment
SeeWebsiteForEmail at erdani.com
Mon Jan 28 19:58:24 UTC 2019
On 1/28/19 1:00 PM, Andrei Alexandrescu wrote:
> On 1/24/19 3:01 PM, kinke wrote:
>> On Thursday, 24 January 2019 at 09:49:14 UTC, Manu wrote:
>>> We discussed and concluded that one mechanism to mitigate this issue
>>> was already readily available, and it's just that 'out' gains a much
>>> greater sense of identity (which is actually a positive side-effect if
>>> you ask me!).
>>> You have a stronger motivation to use 'out' appropriately, because it
>>> can issue compile errors if you accidentally supply an rvalue.
>> `out` with current semantics cannot be used as drop-in replacement for
>> shared in-/output ref params, as `out` params are default-initialized
>> on entry. Ignoring backwards compatibility for a second, I think
>> getting rid of that would actually be beneficial (most args are
>> probably already default-initialized by the callee in the line above
>> the call...) - and I'd prefer an explicitly required `out` at the call
>> site (C# style), to make the side effect clearly visible.
>> I'd have otherwise proposed a `@noRVal` param UDA, but redefining
>> `out` is too tempting indeed. ;)
> It seems to me that a proposal adding the "@rvalue" attribute in
> function signatures to each parameter that would accept either an rvalue
> or an lvalue would be easy to argue.
> - No exposing existing APIs to wrong uses
> - The function's writer makes the decision ("I'm fine with this function
> taking an rvalue")
> - Appears in the function's documentation
> - Syntax is light and localized where it belongs
> - Scales well with number of parameters
> - Transparent to callers
> Whether existing keyword combinations ("in", "out", "ref" etc) could be
> used is a secondary point.
> The advantage is there's a simple and clear path forward for API
> definition and use.
One more thought.
The main danger is restricted to a specific conversion: lvalue of type T
is converted to ref of type U. That way both the caller and the function
writer believe the value gets updated, when in fact it doesn't. Consider:
real modf(real x, ref real i);
Stores integral part in i, returns the fractional part. At this point
there are two liabilities:
1. User passes the wrong parameter type:
double frac = modf(x, integral);
// oops, integral is always NaN
The function silently converts integral from double to real and passes
the resulting temporary into the function. The temporary is filled and
lost, leaving user's value unchanged.
2. The API gets changed:
// Fine, let's use double
real modf(real x, ref double i);
At this point all correct callers are silently broken - everybody who
correctly used a real for the integral part now has their call broken
(real implicitly converts to a double temporary, and the change does not
propagate to the user's value).
(If the example looks familiar it may be because of
So it seems that the real problem is that the participants wrongly
believe an lvalue is updated.
But let's say the caller genuinely doesn't care about the integral part.
To do so is awkward:
double frac = modf(x, unused);
That code isn't any better or less dangerous than:
double frac = modf(x, double());
Here the user created willingly created an unnamed temporary of type
double. Given that there's no doubt the user is not interested in that
value after the call, the compiler could (in a proposed semantics) allow
the conversion of the unnamed temporary to ref.
TL;DR: it could be argued that the only dangerous conversions are lvalue
-> temp rvalue -> ref, so only disable those. The conversion rvalue ->
temp rvalue -> ref is not dangerous because the starting value on the
caller side could not be inspected after the call anyway.
More information about the Digitalmars-d-announce