www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Why do struct literals count as lvalues?

reply Trass3r <un known.com> writes:
struct A {}
static A bar()
{
     return A();
}
void foo(ref A a) {}
void main()
{
     foo(A());   // works
     foo(bar()); // doesn't
}

Where's the difference?
Aug 18 2011
next sibling parent kennytm <kennytm gmail.com> writes:
Trass3r <un known.com> wrote:
 struct A {}
 static A bar()
 {
     return A();
 }
 void foo(ref A a) {}
 void main()
 {
     foo(A());   // works
     foo(bar()); // doesn't
 }
 
 Where's the difference?

The difference -- you've answered yourself in the title ;). Reason why struct literals are lvalues -- because Walter and others believe this is valid. Check the discussion in bugzilla issues 5178 and 5889. BTW, C99's compound literals also give lvalues.
Aug 18 2011
prev sibling next sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Thursday, August 18, 2011 12:49 kennytm wrote:
 Trass3r <un known.com> wrote:
 struct A {}
 static A bar()
 {
 
 return A();
 
 }
 void foo(ref A a) {}
 void main()
 {
 
 foo(A()); // works
 foo(bar()); // doesn't
 
 }
 
 Where's the difference?

The difference -- you've answered yourself in the title ;). Reason why struct literals are lvalues -- because Walter and others believe this is valid. Check the discussion in bugzilla issues 5178 and 5889.

Yeah. I don't understand why a struct literal would be an lvalue. It's a temporary. What possible value does it have? It's not a variable. It doesn't refer to a variable. Why would you be able to assign to anything which is not a variable (or indirectly refers to one - e.g. with ref)? The result is incredibly confusing - especially when you mix const ref into the mix. I grant you that changing it now would break code - especially given http://d.puremagic.com/issues/show_bug.cgi?id=3659 - but it serves no real purpose except to confuse as far as I can see. - Jonathan M Davis
Aug 18 2011
prev sibling next sibling parent Trass3r <un known.com> writes:
Am 18.08.2011, 22:19 Uhr, schrieb Jonathan M Davis <jmdavisProg gmx.com>:
 Yeah. I don't understand why a struct literal would be an lvalue. It's a
 temporary. What possible value does it have? It's not a variable. It  
 doesn't
 refer to a variable. Why would you be able to assign to anything which  
 is not
 a variable (or indirectly refers to one - e.g. with ref)?

I don't understand it either. It only makes sense with const ref.
Aug 18 2011
prev sibling next sibling parent Ali =?iso-8859-1?q?=C7ehreli?= <acehreli yahoo.com> writes:
On Thu, 18 Aug 2011 22:33:47 +0200, Trass3r wrote:

 Am 18.08.2011, 22:19 Uhr, schrieb Jonathan M Davis
 <jmdavisProg gmx.com>:
 Yeah. I don't understand why a struct literal would be an lvalue. It's
 a temporary. What possible value does it have? It's not a variable. It
 doesn't
 refer to a variable. Why would you be able to assign to anything which
 is not
 a variable (or indirectly refers to one - e.g. with ref)?

I don't understand it either. It only makes sense with const ref.

(I suspect that I am confusing issues here; still... :) ) C++11 added rvalue references. They are useful at least for move semantics. (Andrei had worked hard to find an non-intrusive solution but had to settle with an intrusive one: http://drdobbs.com/184403855 ) I remember reading about this in the TDPL. 7.1.3.5 is on topic. Here is an excerpt from page 251: "All anonymous rvalues are moved, not copied. A call to this(this) is never inserted when the source is an anonymous rvalue ..." Good. And when we want to move a named variable, there is std.algorithm.move: kun(move(w)); Ali
Aug 18 2011
prev sibling next sibling parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Thursday, August 18, 2011 13:33 Trass3r wrote:
 Am 18.08.2011, 22:19 Uhr, schrieb Jonathan M Davis <jmdavisProg gmx.com>:
 Yeah. I don't understand why a struct literal would be an lvalue. It's a
 temporary. What possible value does it have? It's not a variable. It
 doesn't
 refer to a variable. Why would you be able to assign to anything which
 is not
 a variable (or indirectly refers to one - e.g. with ref)?

I don't understand it either. It only makes sense with const ref.

It doesn't even make sense with const ref IMHO - not unless you're going to allow const ref to be bound to temporaries in general. I see _zero_ reason to treat a newly constructed object as any different from one which is returned from a function. It just confuses things. And in a sense, a newly constructed object _is_ returned by a function, it's just that it's the constructor rather than a normal function. - Jonathan M Davis
Aug 18 2011
parent reply bearophile <bearophileHUGS lycos.com> writes:
Jonathan M Davis:

 I see _zero_ reason to 
 treat a newly constructed object as any different from one which is returned 
 from a function.

I have not followed fully this thread, so I am not sure what you are talking about, so sorry if I am misunderstanding the whole topic. I assume you want to disallow code like: struct Vect { float[4] data = 0.0; // lot of operator overloading here, all arguments are by ref } void foo(ref Vect f) {} void main() { foo(Vect([1,2,3,4])); } Such code is common. In foo() and in all the Vect operators ref is required for performance. This code is quite handy. Forcing me to define and assign a Vect to a temporary variable in main is not handy. Think about writing expressions that use Vects with operator overloading, that use ref everywhere. If you can't pass and give Vects defined inside the expressions the code becomes quite more hairy. Bye, bearophile
Aug 18 2011
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 08/19/2011 02:16 PM, Steven Schveighoffer wrote:
 On Thu, 18 Aug 2011 18:56:01 -0400, bearophile
 <bearophileHUGS lycos.com> wrote:

 Jonathan M Davis:

 I see _zero_ reason to
 treat a newly constructed object as any different from one which is
 returned
 from a function.

I have not followed fully this thread, so I am not sure what you are talking about, so sorry if I am misunderstanding the whole topic. I assume you want to disallow code like: struct Vect { float[4] data = 0.0; // lot of operator overloading here, all arguments are by ref } void foo(ref Vect f) {} void main() { foo(Vect([1,2,3,4])); } Such code is common. In foo() and in all the Vect operators ref is required for performance. This code is quite handy. Forcing me to define and assign a Vect to a temporary variable in main is not handy. Think about writing expressions that use Vects with operator overloading, that use ref everywhere. If you can't pass and give Vects defined inside the expressions the code becomes quite more hairy.

I believe auto ref is slated to fix this problem. -Steve

auto ref currently treats struct literals as lvalues too. (therefore, as ref). And passing by value would be considerably less efficient for large structs. As I understand it, the only issue with allowing literals and function results as lvalues is that generic code cannot detect if it is working with a temporary or not. I don't know if this would be useful in D though. each code segment of the form: void foo(ref S); ... foo(S(...)); is equivalent to one explicitly declaring the temporary: {auto __temp=S(...); foo(__temp);} The difference is that the first is more pleasant to write. If temporaries would become rvalues everyone would always have to write the second form manually. So imho it is just a syntax sugar issue. I'd actually argue that ref-passing should work for arbitrary function results too.
Aug 19 2011
next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 08/19/2011 03:04 PM, kenji hara wrote:
 2011/8/19 Timon Gehr<timon.gehr gmx.ch>:
 On 08/19/2011 02:16 PM, Steven Schveighoffer wrote:
 On Thu, 18 Aug 2011 18:56:01 -0400, bearophile
 <bearophileHUGS lycos.com>  wrote:

 Jonathan M Davis:

 I see _zero_ reason to
 treat a newly constructed object as any different from one which is
 returned
 from a function.

I have not followed fully this thread, so I am not sure what you are talking about, so sorry if I am misunderstanding the whole topic. I assume you want to disallow code like: struct Vect { float[4] data = 0.0; // lot of operator overloading here, all arguments are by ref } void foo(ref Vect f) {} void main() { foo(Vect([1,2,3,4])); } Such code is common. In foo() and in all the Vect operators ref is required for performance. This code is quite handy. Forcing me to define and assign a Vect to a temporary variable in main is not handy. Think about writing expressions that use Vects with operator overloading, that use ref everywhere. If you can't pass and give Vects defined inside the expressions the code becomes quite more hairy.

I believe auto ref is slated to fix this problem. -Steve

auto ref currently treats struct literals as lvalues too. (therefore, as ref). And passing by value would be considerably less efficient for large structs. As I understand it, the only issue with allowing literals and function results as lvalues is that generic code cannot detect if it is working with a temporary or not. I don't know if this would be useful in D though. each code segment of the form: void foo(ref S); ... foo(S(...)); is equivalent to one explicitly declaring the temporary: {auto __temp=S(...); foo(__temp);} The difference is that the first is more pleasant to write. If temporaries would become rvalues everyone would always have to write the second form manually. So imho it is just a syntax sugar issue. I'd actually argue that ref-passing should work for arbitrary function results too.

As far as I know, Andrei's original 'auto ref' means always receive argument *by reference* either it is lvalue or rvalue, and it works with non template function. But, unfortunately, it doesn't exist in current D. Kenji Hara

Oh, interesting. So why were they implemented with different semantics then? Also, how would they work in a non-templated function? Would they just be conservatively treated as rvalues in the function body?
Aug 19 2011
prev sibling parent reply kennytm <kennytm gmail.com> writes:
Timon Gehr <timon.gehr gmx.ch> wrote:

 auto ref currently treats struct literals as lvalues too. (therefore, as
 ref). And passing by value would be considerably less efficient for large
structs.
 
 As I understand it, the only issue with allowing literals and function
 results as lvalues is that generic code cannot detect if it is working
 with a temporary or not. I don't know if this would be useful in D though.
 
 each code segment of the form:
 
 void foo(ref S);
 ...
 foo(S(...));
 
 is equivalent to one explicitly declaring the temporary:
 
 {auto __temp=S(...); foo(__temp);}
 
 The difference is that the first is more pleasant to write. If
 temporaries would become rvalues everyone would always have to write the
 second form manually. So imho it is just a syntax sugar issue.
 
 I'd actually argue that ref-passing should work for arbitrary function results
too.

Here I propose that we go a step further and abolish the notion of rvalue and lvalue entirely, and let the compiler insert necessary temporary variable when needed, so that we can finally write things like uint w; memcpy(w, &5.0f, w.sizeof); 13u = w; </joking>
Aug 19 2011
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 08/19/2011 03:46 PM, kennytm wrote:
 Timon Gehr<timon.gehr gmx.ch>  wrote:

 auto ref currently treats struct literals as lvalues too. (therefore, as
 ref). And passing by value would be considerably less efficient for large
structs.

 As I understand it, the only issue with allowing literals and function
 results as lvalues is that generic code cannot detect if it is working
 with a temporary or not. I don't know if this would be useful in D though.

 each code segment of the form:

 void foo(ref S);
 ...
 foo(S(...));

 is equivalent to one explicitly declaring the temporary:

 {auto __temp=S(...); foo(__temp);}

 The difference is that the first is more pleasant to write. If
 temporaries would become rvalues everyone would always have to write the
 second form manually. So imho it is just a syntax sugar issue.

 I'd actually argue that ref-passing should work for arbitrary function results
too.

Here I propose that we go a step further and abolish the notion of rvalue and lvalue entirely, and let the compiler insert necessary temporary variable when needed, so that we can finally write things like uint w; memcpy(w,&5.0f, w.sizeof); 13u = w; </joking>

And then of course you could do: 5=6; assert(5==6); It hasn't been working since the good ol' days of fortran =(.
Aug 19 2011
parent reply kennytm <kennytm gmail.com> writes:
Timon Gehr <timon.gehr gmx.ch> wrote:
 On 08/19/2011 03:46 PM, kennytm wrote:
 Timon Gehr<timon.gehr gmx.ch>  wrote:
 
 auto ref currently treats struct literals as lvalues too. (therefore, as
 ref). And passing by value would be considerably less efficient for large
structs.
 
 As I understand it, the only issue with allowing literals and function
 results as lvalues is that generic code cannot detect if it is working
 with a temporary or not. I don't know if this would be useful in D though.
 
 each code segment of the form:
 
 void foo(ref S);
 ...
 foo(S(...));
 
 is equivalent to one explicitly declaring the temporary:
 
 {auto __temp=S(...); foo(__temp);}
 
 The difference is that the first is more pleasant to write. If
 temporaries would become rvalues everyone would always have to write the
 second form manually. So imho it is just a syntax sugar issue.
 
 I'd actually argue that ref-passing should work for arbitrary function results
too.

Here I propose that we go a step further and abolish the notion of rvalue and lvalue entirely, and let the compiler insert necessary temporary variable when needed, so that we can finally write things like uint w; memcpy(w,&5.0f, w.sizeof); 13u = w; </joking>

And then of course you could do: 5=6; assert(5==6); It hasn't been working since the good ol' days of fortran =(.

Actually that assignment would just become an expensive no-op because it will be rewritten into (int __lvalue1243 = 5, __lvalue1243) = 6; So the assert would still assert.
Aug 19 2011
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 08/19/2011 04:33 PM, kennytm wrote:
 Timon Gehr<timon.gehr gmx.ch>  wrote:
 On 08/19/2011 03:46 PM, kennytm wrote:
 Timon Gehr<timon.gehr gmx.ch>   wrote:

 auto ref currently treats struct literals as lvalues too. (therefore, as
 ref). And passing by value would be considerably less efficient for large
structs.

 As I understand it, the only issue with allowing literals and function
 results as lvalues is that generic code cannot detect if it is working
 with a temporary or not. I don't know if this would be useful in D though.

 each code segment of the form:

 void foo(ref S);
 ...
 foo(S(...));

 is equivalent to one explicitly declaring the temporary:

 {auto __temp=S(...); foo(__temp);}

 The difference is that the first is more pleasant to write. If
 temporaries would become rvalues everyone would always have to write the
 second form manually. So imho it is just a syntax sugar issue.

 I'd actually argue that ref-passing should work for arbitrary function results
too.

Here I propose that we go a step further and abolish the notion of rvalue and lvalue entirely, and let the compiler insert necessary temporary variable when needed, so that we can finally write things like uint w; memcpy(w,&5.0f, w.sizeof); 13u = w; </joking>

And then of course you could do: 5=6; assert(5==6); It hasn't been working since the good ol' days of fortran =(.

Actually that assignment would just become an expensive no-op because it will be rewritten into (int __lvalue1243 = 5, __lvalue1243) = 6; So the assert would still assert.

Nah, no-ops should be a compile error :o).
Aug 19 2011
prev sibling next sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Thursday, August 18, 2011 15:56 bearophile wrote:
 Jonathan M Davis:
 I see _zero_ reason to
 treat a newly constructed object as any different from one which is
 returned from a function.

I have not followed fully this thread, so I am not sure what you are talking about, so sorry if I am misunderstanding the whole topic. I assume you want to disallow code like: struct Vect { float[4] data = 0.0; // lot of operator overloading here, all arguments are by ref } void foo(ref Vect f) {} void main() { foo(Vect([1,2,3,4])); } Such code is common. In foo() and in all the Vect operators ref is required for performance. This code is quite handy. Forcing me to define and assign a Vect to a temporary variable in main is not handy. Think about writing expressions that use Vects with operator overloading, that use ref everywhere. If you can't pass and give Vects defined inside the expressions the code becomes quite more hairy.

That may be, but it doesn't refer to an actual variable, so ref makes no sense IMHO, and regardless, the return value of a function is just as hairy. So, with the current situation foo(Vect([1, 2, 3, 4])); works, but foo(func()); doesn't (assuming that func returns ' Vec). That makes no sense to me. Why treat one differently than the other? It's just confusing. - Jonathan M Davis
Aug 18 2011
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Thu, 18 Aug 2011 18:56:01 -0400, bearophile <bearophileHUGS lycos.com>  
wrote:

 Jonathan M Davis:

 I see _zero_ reason to
 treat a newly constructed object as any different from one which is  
 returned
 from a function.

I have not followed fully this thread, so I am not sure what you are talking about, so sorry if I am misunderstanding the whole topic. I assume you want to disallow code like: struct Vect { float[4] data = 0.0; // lot of operator overloading here, all arguments are by ref } void foo(ref Vect f) {} void main() { foo(Vect([1,2,3,4])); } Such code is common. In foo() and in all the Vect operators ref is required for performance. This code is quite handy. Forcing me to define and assign a Vect to a temporary variable in main is not handy. Think about writing expressions that use Vects with operator overloading, that use ref everywhere. If you can't pass and give Vects defined inside the expressions the code becomes quite more hairy.

I believe auto ref is slated to fix this problem. -Steve
Aug 19 2011
prev sibling next sibling parent kenji hara <k.hara.pg gmail.com> writes:
2011/8/19 Timon Gehr <timon.gehr gmx.ch>:
 On 08/19/2011 02:16 PM, Steven Schveighoffer wrote:
 On Thu, 18 Aug 2011 18:56:01 -0400, bearophile
 <bearophileHUGS lycos.com> wrote:

 Jonathan M Davis:

 I see _zero_ reason to
 treat a newly constructed object as any different from one which is
 returned
 from a function.

I have not followed fully this thread, so I am not sure what you are talking about, so sorry if I am misunderstanding the whole topic. I assume you want to disallow code like: struct Vect { float[4] data = 0.0; // lot of operator overloading here, all arguments are by ref } void foo(ref Vect f) {} void main() { foo(Vect([1,2,3,4])); } Such code is common. In foo() and in all the Vect operators ref is required for performance. This code is quite handy. Forcing me to define and assign a Vect to a temporary variable in main is not handy. Think about writing expressions that use Vects with operator overloading, that use ref everywhere. If you can't pass and give Vects defined inside the expressions the code becomes quite more hairy.

I believe auto ref is slated to fix this problem. -Steve

auto ref currently treats struct literals as lvalues too. (therefore, as ref). And passing by value would be considerably less efficient for large structs. As I understand it, the only issue with allowing literals and function results as lvalues is that generic code cannot detect if it is working with a temporary or not. I don't know if this would be useful in D though. each code segment of the form: void foo(ref S); ... foo(S(...)); is equivalent to one explicitly declaring the temporary: {auto __temp=S(...); foo(__temp);} The difference is that the first is more pleasant to write. If temporaries would become rvalues everyone would always have to write the second form manually. So imho it is just a syntax sugar issue. I'd actually argue that ref-passing should work for arbitrary function results too.

As far as I know, Andrei's original 'auto ref' means always receive argument *by reference* either it is lvalue or rvalue, and it works with non template function. But, unfortunately, it doesn't exist in current D. Kenji Hara
Aug 19 2011
prev sibling next sibling parent kenji hara <k.hara.pg gmail.com> writes:
2011/8/19 Timon Gehr <timon.gehr gmx.ch>:
 Oh, interesting. So why were they implemented with different semantics then?

I don't know the background, sorry.
 Also, how would they work in a non-templated function?

receiving a rvalue argument.
 Would they just be conservatively treated as rvalues in the function body?

if it is a parameter. The follows are my opinion: D might be designed as which ref parameter receives only lvalue, and non-ref parameter receives only rvalue. Today ref storage class also means that the parameter binds corresponding argument by reference, but it is not a language design, but that is performance decision. It seems to me that ref storage class is designed for automatic move semantics. According to my think, struct literal should be rvalue. That is temporary, and we want to move it automatically. Kenji Hara
Aug 19 2011
prev sibling parent Trass3r <un known.com> writes:
Am 19.08.2011, 14:50 Uhr, schrieb Timon Gehr <timon.gehr gmx.ch>:
 void foo(ref S);
 ...
 foo(S(...));

 is equivalent to one explicitly declaring the temporary:

 {auto __temp=S(...); foo(__temp);}

 The difference is that the first is more pleasant to write. If  
 temporaries would become rvalues everyone would always have to write the  
 second form manually. So imho it is just a syntax sugar issue.

 I'd actually argue that ref-passing should work for arbitrary function  
 results too.

+1, but const should be required.
Aug 19 2011