www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - auto ref escaping local variable

reply Robert burner Schadek <rburners gmail.com> writes:
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
Jan 23
next sibling parent reply Manu via Digitalmars-d <digitalmars-d puremagic.com> writes:
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; } ??
Jan 23
parent Robert burner Schadek <rburners gmail.com> writes:
Nice idea, but didn't work either. Just got more errors. And my 
eyes hurt now.
Jan 23
prev sibling parent reply Stefan Koch <uplink.coder googlemail.com> writes:
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.
Jan 23
parent reply Patrick Schluter <Patrick.Schluter bbox.fr> writes:
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).
Jan 24
parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
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
Jan 24
next sibling parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
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
Jan 24
parent =?UTF-8?B?Tm9yZGzDtnc=?= <per.nordlow gmail.com> writes:
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.
Jan 24
prev sibling parent reply Jonathan M Davis via Digitalmars-d <digitalmars-d puremagic.com> writes:
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
Jan 24
parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
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
Jan 24
parent Jonathan M Davis via Digitalmars-d <digitalmars-d puremagic.com> writes:
On Tuesday, January 24, 2017 11:16:21 Ali ehreli via Digitalmars-d wrote:
 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
 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.
In general, I think that the guideline is to not bother with ref at all if you're not explicitly trying to get a value back. If you have a struct that's expensive enough to copy around that you need ref, then maybe it shouldn't be a struct on the stack. And if you care about optimizing stuff enough to use ref to avoid copies, then you should understand it well enough to understand the consequences of using it. In general, I think that the place for auto ref is when you're trying to forward ref-ness like when you're wrapping a range, and you want the ref-ness of the wrapped front to be passed on to the wrapper range _if_ it returns by ref, but you don't want it to be ref if it's not ref, and you don't want to do a bunch of static ifs to make it work for both. And if you're looking to have a function that accepts both rvalues an lvalues, auto ref makes sense so long as the function doesn't mutate its arguments. Adding const is nice in that it then gurantees that it doesn't, but it's so restrictive that it usually makes no sense for generic code (at least not if it's dealing with arbitrary types as opposed to a specific group of known types like all integer types). So, using auto ref in place of const& in C++ makes sense so long as you're willing to be careful about not mutating the argument, and const auto ref _can_ make sense, but it's restrictive enough that it probably doesn't. And as you indicated, using auto ref with a function that either might or will mutate its argument is likely to be rarely useful. It makes sense when you're looking to pass ref-ness along (which makes sense in some generic code but most code isn't going to want to do that), and it makes sense if you're paranoid about unnecessary copies and are willing to force the caller to make a copy if they don't want their variable mutated when it's passed in (since then copying only happens if the caller makes it happen), but that puts an unusual and arguably error-prone burden on the caller. So, in general, I would expect that auto ref would be used when either passing along refness or when the programmer wanted an equivalent to const& and was willing to risk mutation occuring by accident. Skipping auto ref and manually overriding the function like you were suggesting doesn't fix any of these complications though. It just makes them more explicit (and thus possibly more clear to the programmer if they don't understand auto ref enough), and it makes it so that the functions can be virtual. Aside from when you need to pass on ref-ness, what you want in principle is auto ref const, but const is just too restrictive to work in the general case. So, I can't possibly recommend to anyone that they start slapping const on function parameters by default, auto ref or not. But all of these complications are part of why I would simply recommond _not_ using ref unless you specifically _want_ the argument to be mutated and that's part of the function's API or if you know what you're doing and know that you need to avoid the cost of the copy. And just don't have structs that are expensive to copy. Phobos already tends to assume that - especially for ranges. And it's so incredibly easy to accidentally copy an object if you mess up with ref that relying on getting ref right doesn't seem like a great solution in general. Also, D has move semantics built into the language, making it so that expensive copying is not as big a problem in D as it is in C++ (particularly C++98). So, I would start by just not using ref, and if profiling indicated that I had a struct that was too expensive to be copying around, I would then look at either putting it on the heap and avoiding the whole problem or using ref and auto ref to avoid copies, but then I'm taking upon myself the burden of making sure that I get ref right enough that I don't end up with unintended copies too frequently.
 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.
const is great in principle, but it is so restrictive in D as to be borderline useless. If you're just dealing with built-in types, it works reasonably well. But as soon as you have user-defined types and indirections, then life gets disgusting fast. Postblit constructors don't work with const. Ranges don't work with const. const tends be viral in that once something is const, you can't get anything non-const out of it, and it's difficult to do anything like tail-const outside of arrays, which the language understands well enough that it makes tail-const work for them. Ref-counting doesn't work with const. If your container is const (or your reference to the container is const), it's going to be really hard to get a range over that container - even more so if you want the range to detect when the container is mutated out from under it and protect you like an iterator would in Java. The list of stuff that doesn't work with const just piles up as your program becomes more complicated. In theory, we might be able to fix some of these problems - like if we could figure how to get postblit constructors to work with const or if we could figure out some way for a range to indicate how it could be converted to a tail-const variant of itself - but once you have transitive const, it locks everything down way more than occurs in C++, and I think that a number of the problems with const are simply insurmountable as long as const has no backdoors. You're basically getting immutable but without the benefits. So, I'm all for using const where it works, but I'm not at all in a hurry to slap it on anything where the types aren't well-known, and it's the sort of thing that I expect to have to be removed at some point if I start using it on user-defined types. And if you're using ranges, then const pretty much goes out the window right there. So, while I would love to be able to use const more (if fact, the whole reason that I started off with D2 back in 2008 rather than D1 was because D2 had const, and D1 didn't), experience has shown that D's const is simply too restrictive to be useful in the general case. If you try very hard to use it, you can use it, but odds are that you're simply not going to be able to use it on most code, and I'm almost to the point that I simply wouldn't bother with it aside from making local variables of built-in types const. I do still try and use it on member functions where it's clear that it will work, but increasingly, I just don't bother. - Jonathan M Davis
Jan 24