May 06, 2013
On 5/6/13 12:48 PM, Steven Schveighoffer wrote:
> On Mon, 06 May 2013 06:43:38 -0700, Andrei Alexandrescu
> <SeeWebsiteForEmail@erdani.org> wrote:
>
>> I think we can technically make the overloading work while also
>> allowing binding rvalues to ref. But that wouldn't help any. Consider:
>>
>> ref int min(ref int a, ref int b) { return b < a ? b : a; }
>> ...
>> int x;
>> fun(min(x, 100));
>>
>> Here the result of min may be bound to an lvalue or an rvalue
>> depending on a condition. In the latter case, combined with D's
>> propensity to destroy temporaries too early (immediately after
>> function calls), the behavior is silently undefined; the code may pass
>> unittests.
>
> Wouldn't the new runtime check fix this?

Depends how you define "fix". It would be a possibly rare bounds check violation on completely innocuous code.

>> This is a known issue in C++. Allowing loose binding of rvalues to ref
>> not only inherits C++'s mistake, but also adds a fresh one.
>
> I thought C++ would handle this kind of code. I remember being able to
> use references to rvalues in ways that were unintuitive, but not undefined.

template <class T> const T& min(const T& a, const T& b) {
    return b < a ? b : a;
}
...
int x = ...;
auto & weird = min(x, 100);

Have a nice day :o).


Andrei
May 06, 2013
On 5/6/13 12:48 PM, Steven Schveighoffer wrote:

(your clock seems to be messed up)

Andrei
May 06, 2013
On Mon, 06 May 2013 10:07:01 -0400, Andrei Alexandrescu <SeeWebsiteForEmail@erdani.org> wrote:

> On 5/6/13 12:48 PM, Steven Schveighoffer wrote:
>
> (your clock seems to be messed up)
>
> Andrei

Could be the time change, haven't rebooted my Mac since flying back.  My clock is correct, but Opera may be confused.

This is a test message, I restarted Opera.

-Steve
May 06, 2013
On Mon, 06 May 2013 10:05:48 -0400, Andrei Alexandrescu <SeeWebsiteForEmail@erdani.org> wrote:

> On 5/6/13 12:48 PM, Steven Schveighoffer wrote:
>> On Mon, 06 May 2013 06:43:38 -0700, Andrei Alexandrescu
>> <SeeWebsiteForEmail@erdani.org> wrote:
>>
>>> I think we can technically make the overloading work while also
>>> allowing binding rvalues to ref. But that wouldn't help any. Consider:
>>>
>>> ref int min(ref int a, ref int b) { return b < a ? b : a; }
>>> ...
>>> int x;
>>> fun(min(x, 100));
>>>
>>> Here the result of min may be bound to an lvalue or an rvalue
>>> depending on a condition. In the latter case, combined with D's
>>> propensity to destroy temporaries too early (immediately after
>>> function calls), the behavior is silently undefined; the code may pass
>>> unittests.
>>
>> Wouldn't the new runtime check fix this?
>
> Depends how you define "fix". It would be a possibly rare bounds check violation on completely innocuous code.

By "completely innocuous" you mean valid?  I don't think the above is valid.

>
>>> This is a known issue in C++. Allowing loose binding of rvalues to ref
>>> not only inherits C++'s mistake, but also adds a fresh one.
>>
>> I thought C++ would handle this kind of code. I remember being able to
>> use references to rvalues in ways that were unintuitive, but not undefined.
>
> template <class T> const T& min(const T& a, const T& b) {
>      return b < a ? b : a;
> }
> ...
> int x = ...;
> auto & weird = min(x, 100);
>
> Have a nice day :o).

It seems to compile and work for me, but I don't know what the point is, since you are being mysterious :)

A long time ago I wrote a logging feature for C++ that returned an rvalue (maybe it was an rvalue reference, it was a long time ago, and I don't have the code anymore).  That would collect log messages via the << operator, and then when the line was through, the destructor would output that line to the logger.  The logging object fetched would either be a dummy no-output object, or a real logger, depending on the logging level selected.  If the logger was disabled, no message was constructed, making it somewhat lazy (any expressions in the line would obviously be executed, just like any standard logger).  It worked without a hitch as long as we used it.  The rvalue stayed allocated and valid throughout the whole line, even though it was passed into each << operation by reference.

-Steve
May 06, 2013
On 5/6/13 10:31 AM, Steven Schveighoffer wrote:
> On Mon, 06 May 2013 10:05:48 -0400, Andrei Alexandrescu
> <SeeWebsiteForEmail@erdani.org> wrote:
>
>> On 5/6/13 12:48 PM, Steven Schveighoffer wrote:
>>> On Mon, 06 May 2013 06:43:38 -0700, Andrei Alexandrescu
>>> <SeeWebsiteForEmail@erdani.org> wrote:
>>>
>>>> I think we can technically make the overloading work while also
>>>> allowing binding rvalues to ref. But that wouldn't help any. Consider:
>>>>
>>>> ref int min(ref int a, ref int b) { return b < a ? b : a; }
>>>> ...
>>>> int x;
>>>> fun(min(x, 100));
>>>>
>>>> Here the result of min may be bound to an lvalue or an rvalue
>>>> depending on a condition. In the latter case, combined with D's
>>>> propensity to destroy temporaries too early (immediately after
>>>> function calls), the behavior is silently undefined; the code may pass
>>>> unittests.
>>>
>>> Wouldn't the new runtime check fix this?
>>
>> Depends how you define "fix". It would be a possibly rare bounds check
>> violation on completely innocuous code.
>
> By "completely innocuous" you mean valid? I don't think the above is valid.

I meant valid-looking.

>>>> This is a known issue in C++. Allowing loose binding of rvalues to ref
>>>> not only inherits C++'s mistake, but also adds a fresh one.
>>>
>>> I thought C++ would handle this kind of code. I remember being able to
>>> use references to rvalues in ways that were unintuitive, but not
>>> undefined.
>>
>> template <class T> const T& min(const T& a, const T& b) {
>> return b < a ? b : a;
>> }
>> ...
>> int x = ...;
>> auto & weird = min(x, 100);
>>
>> Have a nice day :o).
>
> It seems to compile and work for me, but I don't know what the point is,
> since you are being mysterious :)

If x > 100, the code is saving a reference to a destroyed temporary. If you couldn't see it, how many do you expect would see similar issues in even simpler and cleaner D code?

> A long time ago I wrote a logging feature for C++ that returned an
> rvalue (maybe it was an rvalue reference, it was a long time ago, and I
> don't have the code anymore). That would collect log messages via the <<
> operator, and then when the line was through, the destructor would
> output that line to the logger. The logging object fetched would either
> be a dummy no-output object, or a real logger, depending on the logging
> level selected. If the logger was disabled, no message was constructed,
> making it somewhat lazy (any expressions in the line would obviously be
> executed, just like any standard logger). It worked without a hitch as
> long as we used it. The rvalue stayed allocated and valid throughout the
> whole line, even though it was passed into each << operation by reference.

Not relevant.


Andrei
May 06, 2013
On Mon, 06 May 2013 10:40:06 -0400, Andrei Alexandrescu <SeeWebsiteForEmail@erdani.org> wrote:

> On 5/6/13 10:31 AM, Steven Schveighoffer wrote:

>> By "completely innocuous" you mean valid? I don't think the above is valid.
>
> I meant valid-looking.

OK.

>> It seems to compile and work for me, but I don't know what the point is,
>> since you are being mysterious :)
>
> If x > 100, the code is saving a reference to a destroyed temporary. If you couldn't see it, how many do you expect would see similar issues in even simpler and cleaner D code?

No, I was wondering whether the compiler detects this and keeps the temporary in scope (after all, it is in control of that temporary's lifetime).  I called cout with that temporary as the reference, and it seems to not have clobbered it (outputs 100).  I have not had such "lucky" experience with D.  Coming from the perspective of a complete compiler ignoramus, I have no idea what is really happening :)  I know that it's common practice to throw rvalues and catch them as references, which seems to be handled correctly by the C++ compiler.

>
>> A long time ago I wrote a logging feature for C++ that returned an
>> rvalue (maybe it was an rvalue reference, it was a long time ago, and I
>> don't have the code anymore). That would collect log messages via the <<
>> operator, and then when the line was through, the destructor would
>> output that line to the logger. The logging object fetched would either
>> be a dummy no-output object, or a real logger, depending on the logging
>> level selected. If the logger was disabled, no message was constructed,
>> making it somewhat lazy (any expressions in the line would obviously be
>> executed, just like any standard logger). It worked without a hitch as
>> long as we used it. The rvalue stayed allocated and valid throughout the
>> whole line, even though it was passed into each << operation by reference.
>
> Not relevant.

How so?  I thought the point is you were saying that we couldn't handle passing a ref bound to an rvalue to another function (because D destroys it early?), that is precisely what I did.  I felt it was completely on-point.

-Steve
May 06, 2013
On 5/6/13 11:12 AM, Steven Schveighoffer wrote:
>> If x > 100, the code is saving a reference to a destroyed temporary.
>> If you couldn't see it, how many do you expect would see similar
>> issues in even simpler and cleaner D code?
>
> No, I was wondering whether the compiler detects this and keeps the
> temporary in scope (after all, it is in control of that temporary's
> lifetime).

It can't.

Consider the body of min isn't known (eliminate templates etc). Then what the compiler sees is a function call that returns a const ref. All it can assume is it's a valid reference which it will subsequently bind to the name given by the caller. The reference will refer therefore to a destroyed rvalue (temporaries are destroyed at the end of the full expression).

Your example is irrelevant to this discussion because returning an rvalue and subsequently binding it to a const T& is a completely different scenario. It would be also sound if it weren't for this:

struct A {
  A(const T& x) : a(x) {}
  const T& a;
};

In _this_ case, initializing A with an rvalue of type T compiles and subsequently runs with undefined behavior.

I repeat: binding rvalues to ref would make every mistake C++ has done in the area, and add a few original ones. It is not a simple problem; if it seems, more study is required.


Andrei
May 06, 2013
On 5/6/13 11:31 AM, Andrei Alexandrescu wrote:
> struct A {
> A(const T& x) : a(x) {}
> const T& a;
> };
>
> In _this_ case, initializing A with an rvalue of type T compiles and
> subsequently runs with undefined behavior.

I should add I've seen this bug several times (causing mysterious crashes) several times at Facebook. We're working on adding a lint rule to disable the pattern statically.

Binding rvalues to references is fraught with peril.


Andrei

May 06, 2013
On Monday, 6 May 2013 at 13:43:38 UTC, Andrei Alexandrescu wrote:
> I think we can technically make the overloading work while also allowing binding rvalues to ref. But that wouldn't help any. Consider:
>
> ref int min(ref int a, ref int b) { return b < a ? b : a; }
> ...
> int x;
> fun(min(x, 100));
>
> Here the result of min may be bound to an lvalue or an rvalue depending on a condition. In the latter case, combined with D's propensity to destroy temporaries too early (immediately after function calls), the behavior is silently undefined; the code may pass unittests.
>

Now that you mention that, is the proposal for ref safety is really safe ?
May 06, 2013
On 5/6/13 11:34 AM, deadalnix wrote:
> On Monday, 6 May 2013 at 13:43:38 UTC, Andrei Alexandrescu wrote:
>> I think we can technically make the overloading work while also
>> allowing binding rvalues to ref. But that wouldn't help any. Consider:
>>
>> ref int min(ref int a, ref int b) { return b < a ? b : a; }
>> ...
>> int x;
>> fun(min(x, 100));
>>
>> Here the result of min may be bound to an lvalue or an rvalue
>> depending on a condition. In the latter case, combined with D's
>> propensity to destroy temporaries too early (immediately after
>> function calls), the behavior is silently undefined; the code may pass
>> unittests.
>>
>
> Now that you mention that, is the proposal for ref safety is really safe ?

Yes, because it's dynamically checked.

Andrei