digitalmars.D.learn - const ref parameters and r-value references
- Mark Isaacson (17/17) May 02 2014 I'm in the process of learning/practicing D and I noticed
- "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> (16/32) May 02 2014 There is `auto ref`, but it only works for templates and is
- Mark Isaacson (4/4) May 02 2014 Auto ref parameters seem to be just what I need. Thanks! I'd
- Meta (3/7) May 02 2014 The C++ way was deemed not good enough, but nobody knows how to
- "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> (50/54) May 04 2014 I had the impression that your goal was to avoid copying. I
- Timon Gehr (2/5) May 04 2014 It can be constructed in-place.
- "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> (2/10) May 04 2014 Doesn't that depend on the ABI?
- Jonathan M Davis via Digitalmars-d-learn (92/108) May 04 2014 On Fri, 02 May 2014 08:17:06 +0000
- bearophile (6/17) May 04 2014 I think Ada was designed like that, and then have had to fix the
- Mark Isaacson (5/5) May 04 2014 Thanks for the insights! I suppose we'll get a chance to see
- Jonathan M Davis via Digitalmars-d-learn (10/15) May 04 2014 On Sun, 04 May 2014 19:08:27 +0000
I'm in the process of learning/practicing D and I noticed something that seems peculiar coming from a C++ background: If I compile and run: void fun(const ref int x) { //Stuff } unittest { fun(5); //Error! Does not compile } I get the specified error in my unit test. I understand that the cause is that I've attempted to bind ref to an r-value, what's curious is that in C++, the compiler realizes that this is a non-issue because of 'const' and just 'makes it work'. Is there a rationale behind why D does not do this? Is there a way to write 'fun' such that it avoids copies but still pledges const-correctness while also allowing r-values to be passed in? Thanks in advance!
May 02 2014
On Friday, 2 May 2014 at 08:17:09 UTC, Mark Isaacson wrote:I'm in the process of learning/practicing D and I noticed something that seems peculiar coming from a C++ background: If I compile and run: void fun(const ref int x) { //Stuff } unittest { fun(5); //Error! Does not compile } I get the specified error in my unit test. I understand that the cause is that I've attempted to bind ref to an r-value, what's curious is that in C++, the compiler realizes that this is a non-issue because of 'const' and just 'makes it work'. Is there a rationale behind why D does not do this? Is there a way to write 'fun' such that it avoids copies but still pledges const-correctness while also allowing r-values to be passed in?There is `auto ref`, but it only works for templates and is somewhat different: void fun()(auto ref const int x) { // Stuff } unittest { fun(5); // pass by value int a = 5; fun(a); // pass by ref } It generates two functions, with and without ref respectively. Allowing rvalues to bind to ref (not only const) has been discussed on several occasions, but I don't remember the outcome. Here is one discussion: http://forum.dlang.org/thread/ntsyfhesnywfxvzbemwc forum.dlang.org
May 02 2014
Auto ref parameters seem to be just what I need. Thanks! I'd still be curious if anyone has additional information regarding the rationale at play (I'm spoiled, reading TDPL and having each decision explained in text).
May 02 2014
On Friday, 2 May 2014 at 21:29:51 UTC, Mark Isaacson wrote:Auto ref parameters seem to be just what I need. Thanks! I'd still be curious if anyone has additional information regarding the rationale at play (I'm spoiled, reading TDPL and having each decision explained in text).The C++ way was deemed not good enough, but nobody knows how to do it better.
May 02 2014
On Friday, 2 May 2014 at 21:29:51 UTC, Mark Isaacson wrote:Auto ref parameters seem to be just what I need. Thanks! I'd still be curious if anyone has additional information regarding the rationale at play (I'm spoiled, reading TDPL and having each decision explained in text).I had the impression that your goal was to avoid copying. I didn't write it explicitly, but `auto ref` doesn't do that. It just allows you to avoid writing the same function twice, once with ref, and once without. It is essentially equivalent to this: void fun(ref const int x) { // Stuff } void fun(const int x) { // Stuff } This means that you will still get a (bit-wise) copy if you pass in an r-value. But semantically, this is a move, not a copy, so it is potentially cheaper than a copy (if your type has an expensive postblit). You can see this from the following little test program: import std.stdio; struct S { this(this) { writefln("postblit %x", &this); } ~this() { writefln("destructor %x", &this); } } void fun()(auto ref const S x) { writefln("fun: &x = %x", &x); } void main() { writeln("passing lvalue ..."); S s; fun(s); writeln("copying struct ..."); auto x = s; writeln("passing rvalue ..."); fun(S()); writeln("done"); } As you can see, no postblit is called when the struct is passed by value: passing lvalue ... fun: &x = 7ffffa8c73b8 copying struct ... postblit 7ffffa8c73b9 passing rvalue ... fun: &x = 7ffffa8c7370 destructor 7ffffa8c7370 done destructor 7ffffa8c73b9 destructor 7ffffa8c73b8
May 04 2014
On 05/04/2014 12:58 PM, "Marc Schütz" <schuetzm gmx.net>" wrote:This means that you will still get a (bit-wise) copy if you pass in an r-value. But semantically, this is a move, not a copy, so it is potentially cheaper than a copy (if your type has an expensive postblit).It can be constructed in-place.
May 04 2014
On Sunday, 4 May 2014 at 11:15:59 UTC, Timon Gehr wrote:On 05/04/2014 12:58 PM, "Marc Schütz" <schuetzm gmx.net>" wrote:Doesn't that depend on the ABI?This means that you will still get a (bit-wise) copy if you pass in an r-value. But semantically, this is a move, not a copy, so it is potentially cheaper than a copy (if your type has an expensive postblit).It can be constructed in-place.
May 04 2014
On Fri, 02 May 2014 08:17:06 +0000 Mark Isaacson via Digitalmars-d-learn <digitalmars-d-learn puremagic.com> wrote:I'm in the process of learning/practicing D and I noticed something that seems peculiar coming from a C++ background: If I compile and run: void fun(const ref int x) { //Stuff } unittest { fun(5); //Error! Does not compile } I get the specified error in my unit test. I understand that the cause is that I've attempted to bind ref to an r-value, what's curious is that in C++, the compiler realizes that this is a non-issue because of 'const' and just 'makes it work'. Is there a rationale behind why D does not do this? Is there a way to write 'fun' such that it avoids copies but still pledges const-correctness while also allowing r-values to be passed in?By design, in D, ref only accepts lvalues. Unlike in C++, constness has no effect on that. IIRC, the reasons have something to do with being able to tell whether the argument is indeed an lvalue or not as well as there being implementation issues with the fact that const T& foo in C++ doesn't necessary have a variable for foo to refer to. I don't think that I've ever entirely understood the rationale behind it, but Andrei is quite adamant on the matter, and it's been argued over quite a few times. I don't know whether D's choice on the matter is right or not, but it's not changing at this point. Regardless, the problem that it generates is the fact that we don't have a construct which does what C++'s const T& does - i.e. indicate that you want to accept both lvalues and rvalues without making a copy. Andrei suggested auto ref to fix this problem, and Walter implemented it, but he misunderstood what Andrei had meant, so the result was a template-only solution. If you declare auto foo(T)(auto ref T bar) {...} then when you call foo with an lvalue, foo will be instantiated with bar being a ref T, whereas if foo is called with an rvalue, it will be instintiated with bar being a T. In either case, no copy will take place. However, it requires that foo be templated, and it results in a combinatorial explosion of template instantiations as more auto ref parameters are added, and the function is used with various combinations of lvalues and rvalues. The alternative is to declare each overload yourself: auto foo(ref T bar) {...} auto foo(T bar) {...} That doesn't require the function to be templated, and it works for one, maybe two function parameters, but you have the same combinatorial explosion of function declarations as you had with template instantiations with auto ref - except now you're declaring them all explicitly yourself instead of the compiler generating them for you. What has been suggested is that we have a way to mark a non-templated function as accepting both lvalues and rvalues - e.g. auto foo(NewRefThingy T bar) {...} and what it would do is make it so that underneath the hood, foo would actually be auto foo(ref T bar) {...} but instead of giving an error when you pass it an rvalue, it would do the equivalent of auto temp = returnsRValue(); foo(temp); so that foo would have an rvalue. You still wouldn't get any copying happening, you'd only have to declare one function, and you wouldn't get a combinatorial explosion of function declarations or template instantiations. I believe that that is essentially what Andrei originally intended for auto ref to be. The problem is that we don't want to introduce yet another attribute to do this. We could reuse auto ref for it, so you'd do auto foo(auto ref T bar) {...} but that either means that we redefine what auto ref does with templated functions (which would be a problem, because it's actually useful there for other purposes, because it's the closest thing that we have to perfect forwarding at this point), or we make it so that auto ref does something different with normal functions than it does with template functions, which could be confusing, and you might actually like to be able to use the non-templated auto ref solution with templated functions. It should be possible _some_ of the time for the compiler to determine that it can optimize the templated version into the non-templated one (i.e. when it can determine that the forwarding capabilities of thet templated auto ref aren't used), but it's not clear how well that will work, or whether it's an acceptable solution. Walter has suggested that we just redefine ref itself to do what I just described rather than using auto ref or defining a new attribute. However, both Andrei and I argued with him quite a bit over that, because that makes it so that you can't tell whether a ref argument is intended to mutate what's being passed in, or whether it's just an optimization (and you can't just use const in all of the situations where you don't want the mutation, because D's const is far more restrictive than C++'s const). Others agree with us, and there are probably some that agree with Walter, but I don't remember at this point. And Manu was arguing for something with scope ref to solve the problem, though I'm not sure that he had much support on that one, and I can't remember the details at this point. It involved having scope return types and altered the meaning of scope slightly (though it was at least theoretically in the spirit of what scope is supposed to be). But regradless of the merits of his suggestion, Andrei was flat-out against it, because having scope ref would essentially be creating a new attribute, which is unacceptable at this point. In any case, we discussed it quite a bit at dconf last year and in the newsgroup shortly thereafter, but we never came to a decision on what to do. We have what probably should be the solution underneat the hood, just not a way to express it in the language yet. What _did_ come out of dconf last year with regards to ref was a means of making it fully safe like it's supposed to be by adding some runtime checks that would only need to be used under circumstances when the compiler couldn't guarantee that ref wasn't referring to a variable on the stack. But even that hasn't been implemented yet AFAIK - probably in part because while those of us in the discussion at dconf agreed on it, some of the folks in the newsgroup weren't quite so hot on the idea. So, yes, this is something that we should have solved by now, and we have some good ideas on the subject, but we just haven't managed to get our act together on the matter yet. - Jonathan M Davis
May 04 2014
Jonathan M Davis:Andrei suggested auto ref to fix this problem, and Walter implemented it, but he misunderstood what Andrei had meant,I missed this detail of the story :-)Walter has suggested that we just redefine ref itself to do what I just described rather than using auto ref or defining a new attribute. However, both Andrei and I argued with him quite a bit over that, because that makes it so that you can't tell whether a ref argument is intended to mutate what's being passed in, or whether it's just an optimizationI think Ada was designed like that, and then have had to fix the language. So it's not a good idea. Bye, bearophile
May 04 2014
Thanks for the insights! I suppose we'll get a chance to see where things stand at this year's dconf. It's quite interesting that D's concept of r-values seems less developed than C++. Here's hoping that that only results in a better thought out solution.
May 04 2014
On Sun, 04 May 2014 19:08:27 +0000 Mark Isaacson via Digitalmars-d-learn <digitalmars-d-learn puremagic.com> wrote:Thanks for the insights! I suppose we'll get a chance to see where things stand at this year's dconf. It's quite interesting that D's concept of r-values seems less developed than C++. Here's hoping that that only results in a better thought out solution.Well, IIRC, rvalue-references are exactly what exactly what Andrei wants to avoid due the large number of complications that the introduce to the language. Ultimately, we want a solution in D that is simpler but still does the job. We have simpler, but haven't quite sorted out the "still does the job" part. I expect that we'll get there eventually, but we really should have gotten there long before now and haven't. - Jonathan M Davis
May 04 2014