Jump to page: 1 2
Thread overview
auto ref escaping local variable
Jan 24, 2017
Manu
Jan 24, 2017
Stefan Koch
Jan 24, 2017
Patrick Schluter
Jan 24, 2017
Ali Çehreli
Jan 24, 2017
Ali Çehreli
Jan 24, 2017
Nordlöw
Jan 24, 2017
Jonathan M Davis
Jan 24, 2017
Ali Çehreli
Jan 25, 2017
Jonathan M Davis
January 24, 2017
I have this program that used to compile with 72 but with 73 dmd is complaining that
"Error: escaping reference to local variable t"

auto ref f2(T)(auto ref T t, auto ref T s) {
	return t;
}

auto ref f1(T)(auto ref T t, auto ref T s) {
	return f2(t, s);
}

unittest {
	int a = f1(1,2);
}

I'm not sure why, or how to fix that.

https://issues.dlang.org/show_bug.cgi?id=17117
January 24, 2017
On 24 January 2017 at 10:52, Robert burner Schadek via Digitalmars-d < digitalmars-d@puremagic.com> wrote:

> I have this program that used to compile with 72 but with 73 dmd is
> complaining that
> "Error: escaping reference to local variable t"
>
> auto ref f2(T)(auto ref T t, auto ref T s) {
>         return t;
> }
>

Maybe:

auto ref f2(T)(return auto ref T t, auto ref T s) {
        return t;
}

??


January 24, 2017
Nice idea, but didn't work either. Just got more errors. And my eyes hurt now.
January 24, 2017
On Tuesday, 24 January 2017 at 00:52:34 UTC, Robert burner Schadek wrote:
> I have this program that used to compile with 72 but with 73 dmd is complaining that
> "Error: escaping reference to local variable t"
>
> auto ref f2(T)(auto ref T t, auto ref T s) {
> 	return t;
> }
>
> auto ref f1(T)(auto ref T t, auto ref T s) {
> 	return f2(t, s);
> }
>
> unittest {
> 	int a = f1(1,2);
> }
>
> I'm not sure why, or how to fix that.
>
> https://issues.dlang.org/show_bug.cgi?id=17117

Seems to work as expected.
The literals 1,2 cannot be ref.
Therefore it's a normal parameter.
A function parameter is the same as a local
hence retuning a ref to it will cause this to happen.

January 24, 2017
On Tuesday, 24 January 2017 at 06:51:40 UTC, Stefan Koch wrote:
> On Tuesday, 24 January 2017 at 00:52:34 UTC, Robert burner Schadek wrote:
>> I have this program that used to compile with 72 but with 73 dmd is complaining that
>> "Error: escaping reference to local variable t"
>>
>> auto ref f2(T)(auto ref T t, auto ref T s) {
>> 	return t;
>> }
>>
>> auto ref f1(T)(auto ref T t, auto ref T s) {
>> 	return f2(t, s);
>> }
>>
>> unittest {
>> 	int a = f1(1,2);
>> }
>>
>> I'm not sure why, or how to fix that.
>>
>> https://issues.dlang.org/show_bug.cgi?id=17117
>
> Seems to work as expected.
> The literals 1,2 cannot be ref.
> Therefore it's a normal parameter.
> A function parameter is the same as a local
> hence retuning a ref to it will cause this to happen.

Depends on what auto ref is supposed to do, I suppose. What is the heuristic used to determine if it should handle a parameter as a reference or as a copy?
In the case of f1(1,2) it's obvious, as it is impossible to have a reference on the literals, but for f(t, s); both are possible. In the case of int and small parameter types sizes a copy would be faster.
So the question is, does auto ref use a reference as soon as it is possible to have a ref, or should it do so only when it "knows" that it is better for performance? I imagine, it's actually the first option.
(just thinking loud).

January 24, 2017
On 01/24/2017 12:12 AM, Patrick Schluter wrote:
> On Tuesday, 24 January 2017 at 06:51:40 UTC, Stefan Koch wrote:
>> On Tuesday, 24 January 2017 at 00:52:34 UTC, Robert burner Schadek wrote:
>>> I have this program that used to compile with 72 but with 73 dmd is
>>> complaining that
>>> "Error: escaping reference to local variable t"
>>>
>>> auto ref f2(T)(auto ref T t, auto ref T s) {
>>>     return t;
>>> }
>>>
>>> auto ref f1(T)(auto ref T t, auto ref T s) {
>>>     return f2(t, s);
>>> }
>>>
>>> unittest {
>>>     int a = f1(1,2);
>>> }
>>>
>>> I'm not sure why, or how to fix that.
>>>
>>> https://issues.dlang.org/show_bug.cgi?id=17117
>>
>> Seems to work as expected.
>> The literals 1,2 cannot be ref.
>> Therefore it's a normal parameter.
>> A function parameter is the same as a local
>> hence retuning a ref to it will cause this to happen.
>
> Depends on what auto ref is supposed to do, I suppose. What is the
> heuristic used to determine if it should handle a parameter as a
> reference or as a copy?

Lvalues are passed by reference and rvalues are copied.

> In the case of f1(1,2) it's obvious, as it is impossible to have a
> reference on the literals, but for f(t, s); both are possible. In the
> case of int and small parameter types sizes a copy would be faster.
> So the question is, does auto ref use a reference as soon as it is
> possible to have a ref, or should it do so only when it "knows" that it
> is better for performance? I imagine, it's actually the first option.
> (just thinking loud).

No, it has nothing to do with performance.

The problem with auto ref is that in the case of rvalues, what you have is a local variable, which makes it almost given that when it's 'auto ref', it better be 'auto ref const' because you don't want to mutate anyway because your mutations would be lost in the case of rvalues.

I'm under the impression (and started to write a blog post about) that the following two overloads is better than 'auto ref' (const or not). Given S is a struct,

  void foo(const(S));        // Takes rvalue
  void foo(ref const(S));    // Takes lvalue

Those two work with all kinds of S arguments: lvalue, rvalue, imuttable, different kinds of member indirections, etc.

If one wants mutability, or special handling for say, immutable, than add more overloads:

  void foo(const(S));        // Takes rvalue
  void foo(ref const(S));    // Takes lvalue except the following
  void foo(ref S);           // Takes mutable lvalue

etc.

Ali

January 24, 2017
On 01/24/2017 12:47 AM, Ali Çehreli wrote:

> Lvalues are passed by reference and rvalues are copied.

I keep making that mistake! Despite the by-copy syntax, rvalues are moved.

Ali

January 24, 2017
On Tuesday, 24 January 2017 at 08:49:17 UTC, Ali Çehreli wrote:
> On 01/24/2017 12:47 AM, Ali Çehreli wrote:
>
> > Lvalues are passed by reference and rvalues are copied.
>
> I keep making that mistake! Despite the by-copy syntax, rvalues are moved.
>
> Ali

Further note that you can use conditional compilation with `__traits(isRef)` as in

struct S {}

void f()(auto ref const S x)
{
    static if (__traits(isRef, x)) // l-value `x` was passed by ref
    {
        // special treatment of const ref x
    }
    else  // r-value `x` was passed by move
    {
        // `x´ can be reused, for instance in a move-return
    }
}

This is actually a very useful feature in some cases.

Used, for instance, in gmp-d announced yesterday here

http://forum.dlang.org/thread/mwkehzkdbwzygngeaonf@forum.dlang.org

for clever expression template optimization possible. AFAIK a D-only feature.
January 24, 2017
On Tuesday, January 24, 2017 00:47:31 Ali Çehreli via Digitalmars-d wrote:
> On 01/24/2017 12:12 AM, Patrick Schluter wrote:
>  > On Tuesday, 24 January 2017 at 06:51:40 UTC, Stefan Koch wrote:
>  >> On Tuesday, 24 January 2017 at 00:52:34 UTC, Robert burner Schadek
>
> wrote:
>  >>> I have this program that used to compile with 72 but with 73 dmd is
>  >>> complaining that
>  >>> "Error: escaping reference to local variable t"
>  >>>
>  >>> auto ref f2(T)(auto ref T t, auto ref T s) {
>  >>>
>  >>>     return t;
>  >>>
>  >>> }
>  >>>
>  >>> auto ref f1(T)(auto ref T t, auto ref T s) {
>  >>>
>  >>>     return f2(t, s);
>  >>>
>  >>> }
>  >>>
>  >>> unittest {
>  >>>
>  >>>     int a = f1(1,2);
>  >>>
>  >>> }
>  >>>
>  >>> I'm not sure why, or how to fix that.
>  >>>
>  >>> https://issues.dlang.org/show_bug.cgi?id=17117
>  >>
>  >> Seems to work as expected.
>  >> The literals 1,2 cannot be ref.
>  >> Therefore it's a normal parameter.
>  >> A function parameter is the same as a local
>  >> hence retuning a ref to it will cause this to happen.
>  >
>  > Depends on what auto ref is supposed to do, I suppose. What is the
>  > heuristic used to determine if it should handle a parameter as a
>  > reference or as a copy?
>
> Lvalues are passed by reference and rvalues are copied.
>
>  > In the case of f1(1,2) it's obvious, as it is impossible to have a
>  > reference on the literals, but for f(t, s); both are possible. In the
>  > case of int and small parameter types sizes a copy would be faster.
>  > So the question is, does auto ref use a reference as soon as it is
>  > possible to have a ref, or should it do so only when it "knows" that it
>  > is better for performance? I imagine, it's actually the first option.
>  > (just thinking loud).
>
> No, it has nothing to do with performance.
>
> The problem with auto ref is that in the case of rvalues, what you have is a local variable, which makes it almost given that when it's 'auto ref', it better be 'auto ref const' because you don't want to mutate anyway because your mutations would be lost in the case of rvalues.

If you're looking to mutate the argument that's passed in, then it needs to be ref. If you're looking to have a copy to mutate, then it should not be ref. If you don't intend to mutate it, then auto ref works just fine. It just doesn't prevent you from mutating anything. And if you're using auto ref and it's expected that the argument will be mutated, then it needs to make sense for the result to be thrown away as would occur with an rvalue argument.

> I'm under the impression (and started to write a blog post about) that
> the following two overloads is better than 'auto ref' (const or not).
> Given S is a struct,
>
>    void foo(const(S));        // Takes rvalue
>    void foo(ref const(S));    // Takes lvalue
>
> Those two work with all kinds of S arguments: lvalue, rvalue, imuttable, different kinds of member indirections, etc.

I don't see why that would be better than auto ref. It's doing exactly the same thing except that it's const. You're just manually doing what auto ref does. And given how restrictive const is, I would be very slow to mark much of anything with it unless the types are known. And if you want const, then just use const auto ref. The only advantage of

    void foo(const(S));        // Takes rvalue
    void foo(ref const(S));    // Takes lvalue

over const auto ref is that it works with virtual functions. Otherwise, you're just manually doing what const auto ref does - which scales horribly as you add more parameters.

Personally, most of the time, I simply don't worry about the performance of copying. I just pass everything by value and don't use ref unless it's expected that the argument's value will be used and that it will be given a new value as part of the call (in which case, there is no non-ref overload). If profiling indicates that there's too much of a performance hit from copying structs around, then I'll look at using ref or auto ref for performance, but it's not something that I do if I don't need to. It just makes the code messier and more verbose - especially if you use ref instead of auto ref.

I also tend to not bother with const at this point. Too little works with it for it to be worth it most of the time - especially since ranges don't work with it. Built-in types and simple structs can work with it just fine, but much beyond that becomes a bundle of pain really fast, and even simple structs fall flat on their face once you need a postblit constructor. So, I would _not_ be in a hurry to suggest to anyone that they start using const ref or const auto ref anywhere.

- Jonathan M Davis


January 24, 2017
On 01/24/2017 02:03 AM, Jonathan M Davis via Digitalmars-d wrote:
> On Tuesday, January 24, 2017 00:47:31 Ali Çehreli via Digitalmars-d wrote:

>> The problem with auto ref is that in the case of rvalues, what you have
>> is a local variable, which makes it almost given that when it's 'auto
>> ref', it better be 'auto ref const' because you don't want to mutate
>> anyway because your mutations would be lost in the case of rvalues.
>
> If you're looking to mutate the argument that's passed in, then it needs to
> be ref. If you're looking to have a copy to mutate, then it should not be
> ref. If you don't intend to mutate it, then auto ref works just fine. It
> just doesn't prevent you from mutating anything. And if you're using auto
> ref and it's expected that the argument will be mutated, then it needs to
> make sense for the result to be thrown away as would occur with an rvalue
> argument.

Obviously, I know all of that and they are pretty complicated for new programmers.

I just can't imagine what the semantics of a function could be. Do you have an example? So, we're talking about a function that will mutate its argument but the caller sometimes doesn't care. Oh, this sounds like functions from the C era, which take null when the caller does not care.

So, is this the guideline? "Make the argument 'auto ref' when you have something to return in addition to the return value." If so, it's sub-obtimal because the 'auto ref' doesn't have the opportunity of bypassing operations like the C function could:

    if (arg) {
        // Do expensive operation
    }

If I guessed the semantics right, non-const 'auto ref' does not have that luxury.

>> I'm under the impression (and started to write a blog post about) that
>> the following two overloads is better than 'auto ref' (const or not).
>> Given S is a struct,
>>
>>    void foo(const(S));        // Takes rvalue
>>    void foo(ref const(S));    // Takes lvalue
>>
>> Those two work with all kinds of S arguments: lvalue, rvalue, imuttable,
>> different kinds of member indirections, etc.
>
> I don't see why that would be better than auto ref.

Well, if I don't understand the semantics of non-const 'auto ref' then there is only 'auto ref const' to talk about, in which case, the two overloads are better because it's more explicit that one takes rvalue.

> It's doing exactly the
> same thing except that it's const. You're just manually doing what auto ref
> does. And given how restrictive const is, I would be very slow to mark much
> of anything with it unless the types are known. And if you want const, then
> just use const auto ref. The only advantage of
>
>     void foo(const(S));        // Takes rvalue
>     void foo(ref const(S));    // Takes lvalue
>
> over const auto ref is that it works with virtual functions. Otherwise,
> you're just manually doing what const auto ref does - which scales horribly
> as you add more parameters.

Yeah, the issue with non-scaling was one of the reasons why I had stopped turning this into a blog post.

> Personally, most of the time, I simply don't worry about the performance of
> copying. I just pass everything by value and don't use ref unless it's
> expected that the argument's value will be used and that it will be given a
> new value as part of the call (in which case, there is no non-ref overload).
> If profiling indicates that there's too much of a performance hit from
> copying structs around, then I'll look at using ref or auto ref for
> performance, but it's not something that I do if I don't need to. It just
> makes the code messier and more verbose - especially if you use ref instead
> of auto ref.
>
> I also tend to not bother with const at this point. Too little works with it
> for it to be worth it most of the time - especially since ranges don't work
> with it. Built-in types and simple structs can work with it just fine, but
> much beyond that becomes a bundle of pain really fast, and even simple
> structs fall flat on their face once you need a postblit constructor. So, I
> would _not_ be in a hurry to suggest to anyone that they start using const
> ref or const auto ref anywhere.
>
> - Jonathan M Davis
>

const is still engrained in my programming mind due to long exposure to C and C++. I guess D is proving that it's not that essential to be const-correct. This is similar to how private is not as strong and in some cases public is the default.

Ali

« First   ‹ Prev
1 2