www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - DIP 1016 and const ref parameters

reply XavierAP <n3minis-git yahoo.es> writes:
I often use a pattern of having const ref struct parameters (as 
in C++) but this doesn't work in the case of rvalues. The 
workaround of defining an overload that calls its own name is 
terrible. I understand there was a DIP 1016 by Manu asking for 
this case to work. As far as I can tell, this was rejected, but 
later reconsidered, and now Andrei is starting up a new one[1]? 
Apologies but I'm not sure where these discussions are 
centralized. But if anyone has any idea or guess how seriously 
and in what kind of time this could be expected, that would be my 
first side question.

My main learning question is whether the const ref parameter 
pattern is good in D? In C++ I see it everywhere, but are there 
better alternatives, in particular in D, or is there no point 
because some copy elision optimization may be guaranteed? In 
short am I right in writing const ref parameters, or am I doing 
something silly (and as important as this DIP may otherwise be, 
it wouldn't affect me as much as I think)??

As far as I can see, this DIP would be helpful for two use cases: 
const ref, and return ref with method chains. Are there others?
__________
[1] 
https://forum.dlang.org/post/d90a7424-a986-66f1-e889-a9abd55e0e65 erdani.org
Jun 19 2019
parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Wednesday, June 19, 2019 6:33:44 AM MDT XavierAP via Digitalmars-d-learn 
wrote:
 I often use a pattern of having const ref struct parameters (as
 in C++) but this doesn't work in the case of rvalues. The
 workaround of defining an overload that calls its own name is
 terrible. I understand there was a DIP 1016 by Manu asking for
 this case to work. As far as I can tell, this was rejected, but
 later reconsidered, and now Andrei is starting up a new one[1]?
 Apologies but I'm not sure where these discussions are
 centralized. But if anyone has any idea or guess how seriously
 and in what kind of time this could be expected, that would be my
 first side question.

 My main learning question is whether the const ref parameter
 pattern is good in D? In C++ I see it everywhere, but are there
 better alternatives, in particular in D, or is there no point
 because some copy elision optimization may be guaranteed? In
 short am I right in writing const ref parameters, or am I doing
 something silly (and as important as this DIP may otherwise be,
 it wouldn't affect me as much as I think)??

 As far as I can see, this DIP would be helpful for two use cases:
 const ref, and return ref with method chains. Are there others?
 __________
 [1]
 https://forum.dlang.org/post/d90a7424-a986-66f1-e889-a9abd55e0e65 erdani.o
 rg
Even in C++, using const ref is not as good a practice as it once was, because they added move constructors, finally making object moveable. The result is that in many cases, it's actually more efficient to just copy values in C++ rather than use const &, but which is better does depend on the code. As for D, unless you're dealing with large objects, odds are that worrying about passing by value is pointless. D classes are reference types, and D structs have move semantics built-in. So, you don't get as many copies as you would in C++98, and the situation is probably better than newer versions of C++, since IIRC, C++ classes aren't moveable by default, whereas D structs are. In general, you're probably better off just passing by value unless you find that a particular piece of code is inefficient when benchmarking. Either way, you don't want to be slapping const on everything the way you would in C++, because D's const is far more restrictive. So, while it still can be quite useful, odds are that if you start using it heavily, you're going to run into problems fast - especially since casting away const and mutating an object is undefined behavior in D. D's const has no back doors. If something is const, then you can't mutate it unless you also have a mutable reference to the same data. And because const is transitive, you pretty much can't get mutable stuff from const stuff like you frequently can in C++ (e.g. in C++, it's possible to have a const container of mutable objects, wherein D, once part of something is const, everything within that part is const). As for the DIP, I'd suggest watching Andrei's recent dconf talk on the subject: https://www.youtube.com/watch?v=aRvu2JGGn6E&feature=youtu.be - Jonathan M Davis
Jun 19 2019
parent reply XavierAP <n3minis-git yahoo.es> writes:
On Wednesday, 19 June 2019 at 12:55:09 UTC, Jonathan M Davis 
wrote:
 Even in C++, using const ref is not as good a practice as it 
 once was, because they added move constructors, finally making 
 object moveable. The result is that in many cases, it's 
 actually more efficient to just copy values in C++ rather than 
 use const &, but which is better does depend on the code.

 As for D, unless you're dealing with large objects, odds are 
 that worrying about passing by value is pointless. D classes 
 are reference types, and D structs have move semantics 
 built-in. So, you don't get as many copies as you would in 
 C++98, and the situation is probably better than newer versions 
 of C++, since IIRC, C++ classes aren't moveable by default, 
 whereas D structs are. In general, you're probably better off 
 just passing by value unless you find that a particular piece 
 of code is inefficient when benchmarking. Either way, you don't 
 want to be slapping const on everything the way you would in 
 C++, because D's const is far more restrictive. So, while it 
 still can be quite useful, odds are that if you start using it 
 heavily, you're going to run into problems fast - especially 
 since casting away const and mutating an object is undefined 
 behavior in D. D's const has no back doors. If something is 
 const, then you can't mutate it unless you also have a mutable 
 reference to the same data. And because const is transitive, 
 you pretty much can't get mutable stuff from const stuff like 
 you frequently can in C++ (e.g. in C++, it's possible to have a 
 const container of mutable objects, wherein D, once part of 
 something is const, everything within that part is const).

 As for the DIP, I'd suggest watching Andrei's recent dconf talk 
 on the subject:

 https://www.youtube.com/watch?v=aRvu2JGGn6E&feature=youtu.be

 - Jonathan M Davis
I am not talking about cases that would be candidate for moving, or where const would be any problem. If you want an example for the sake of argument: struct Matrix3D { Matrix3D opBinary(string op)(const ref Matrix3D rhs) const; } unittest { auto a = Matrix3D.random; assert(a == a * Matrix3D.identity); assert(a == a + Matrix3D.zeros); } I did watch Andrei's talk, actually this is where I started and learned about the DIP(s), then I was confused that 1016 had been rejected, and smelling that it may be "reopening" I was not sure where I can find the "index" of DIPs under discussion or whatever... :)
 IIRC, C++ classes aren't moveable by default, whereas D structs 
 are.
What do you mean that structs are movable? I know about RVO (in both D and C++, supposedly guaranteed by all compilers in practice, but not by language spec -- why not D?), but what about passing up the stack as here?
Jun 19 2019
parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Wednesday, June 19, 2019 12:28:12 PM MDT XavierAP via Digitalmars-d-learn 
wrote:
 On Wednesday, 19 June 2019 at 12:55:09 UTC, Jonathan M Davis

 wrote:
 Even in C++, using const ref is not as good a practice as it
 once was, because they added move constructors, finally making
 object moveable. The result is that in many cases, it's
 actually more efficient to just copy values in C++ rather than
 use const &, but which is better does depend on the code.

 As for D, unless you're dealing with large objects, odds are
 that worrying about passing by value is pointless. D classes
 are reference types, and D structs have move semantics
 built-in. So, you don't get as many copies as you would in
 C++98, and the situation is probably better than newer versions
 of C++, since IIRC, C++ classes aren't moveable by default,
 whereas D structs are. In general, you're probably better off
 just passing by value unless you find that a particular piece
 of code is inefficient when benchmarking. Either way, you don't
 want to be slapping const on everything the way you would in
 C++, because D's const is far more restrictive. So, while it
 still can be quite useful, odds are that if you start using it
 heavily, you're going to run into problems fast - especially
 since casting away const and mutating an object is undefined
 behavior in D. D's const has no back doors. If something is
 const, then you can't mutate it unless you also have a mutable
 reference to the same data. And because const is transitive,
 you pretty much can't get mutable stuff from const stuff like
 you frequently can in C++ (e.g. in C++, it's possible to have a
 const container of mutable objects, wherein D, once part of
 something is const, everything within that part is const).

 As for the DIP, I'd suggest watching Andrei's recent dconf talk
 on the subject:

 https://www.youtube.com/watch?v=aRvu2JGGn6E&feature=youtu.be

 - Jonathan M Davis
I am not talking about cases that would be candidate for moving, or where const would be any problem. If you want an example for the sake of argument: struct Matrix3D { Matrix3D opBinary(string op)(const ref Matrix3D rhs) const; } unittest { auto a = Matrix3D.random; assert(a == a * Matrix3D.identity); assert(a == a + Matrix3D.zeros); } I did watch Andrei's talk, actually this is where I started and learned about the DIP(s), then I was confused that 1016 had been rejected, and smelling that it may be "reopening" I was not sure where I can find the "index" of DIPs under discussion or whatever... :)
The DIPs are here: https://github.com/dlang/DIPs Aside from looking through the newsgroup/forum for discussions on DIPs, that's pretty much all you're going to find on that. Andrei's talk is the most up-to-date information that we have about this particular DIP.
 IIRC, C++ classes aren't moveable by default, whereas D structs
 are.
What do you mean that structs are movable? I know about RVO (in both D and C++, supposedly guaranteed by all compilers in practice, but not by language spec -- why not D?), but what about passing up the stack as here?
I mean that the object is moved from one place in memory to another rather than copied. There are cases where the compiler needs to take an object and put it somewhere else. RVO may be one of those, though if I understand correctly with RVO, it may just place the return value outside of the function in the first place to avoid needing to move. But regardless of whether RVO causes a move, there are cases where the compiler can move an object rather than copying it. A very simple case of where a move could theoretically happen (though I'm not sure how much this happens in practice) would be with code like void foo() { MyStruct m; bar(m); } void bar(MyStruct m) { ... } bar takes its argument by value. So, it can't take a reference to avoid a copy. However, because when bar is called, it's the last time that m is used inside of foo, rather than copying m to where bar would use it as a parameter, the compiler could choose to move the object to where bar expects its parameter to be rather than copying it - so it could just blit the bits over. In some cases, it may be able to just place m in the right place that bar will get it without it being copied, but with more complicated code, that's not possible. A far more likely case where objects would be moved is with temporaries. e.g. with this code func(foo(), bar(42), baz("hello")); assuming that none of these functions return by ref, func is taking temporaries from several functions, and the spec actually guarantees that they will not be copied. So, if the compiler needs to move them to put them in the right place for func to get them as parameters, it will. In C++98, it really isn't legal for the compiler to move objects. This is because it's legal to have classes/structs with pointers to any part of themselves (e.g. if an object contained a static array, it could also contain a pointer to one of the elements in that array), meaning that if they were moved rather than copied, such pointers would then point to the wrong place, putting the object in an invalid state. This means that C++98 does a lot of copying that shouldn't be necessary and is part of why const& takes rvalues in C++. C++11 added move constructors to solve that problem. So, now, in C++, if an object has a move constructor, and the compiler wants to move it, it can call the move constructor, allowing for the object to do stuff like fix its internal pointers so that they point to the correct place after the object has been moved. D's solution to this was to just say that having a struct with a pointer to its own internals is not supported, and if you do it, your object will end up in an invalid state whenever it's moved. The result is that the compiler is allowed to move structs whenever it decides that it's a good idea. This obviously isn't good for the rare cases where you really do want an object with a pointer to its own internals, but it's fantastic for the typical case, because it gets rid of the need for a lot of copying. So, unlike C++98, objects can be moved freely, and unlike C++11, there's no need for move constructors. This significantly reduced the need for something like const& in D, and actually, one of the main motivators for the DIP to support passing rvalues by ref is for interacting with C++ code that does it. However, the guys at Weka (who are doing lots of low level stuff in their product because of how high performance it is) came up with a use case where they basically needed move constructors. So, one of the Weka employees wrote a DIP for introducing what is essentially the move equivalent of a postblit constructor. Unlike with C++, it wouldn't be necessary for an object to be moveable, but it _would_ allow for structs to essentially have a move constructor for those cases where the default behavior is not enough (similar to how copy constructors and postblit constructors are not necesary if the default copying behavior is enough but _are_ necessary when it isn't). I don't think that the DIP has been implemented yet, but it _has_ been approved. https://github.com/dlang/DIPs/blob/master/DIPs/accepted/DIP1014.md If you really want to know more about moving, I'd suggest that you read up on move constructors and move semantics. There should be plenty of resources online that talk about the issue in C++, and I expect that a number of them explain it better than I do. The key thing here for D is that all structs are considered to be moveable without the need for any kind of move constructor. - Jonathan M Davis
Jun 19 2019
next sibling parent reply Max Haughton <maxhaton gmail.com> writes:
On Wednesday, 19 June 2019 at 19:25:59 UTC, Jonathan M Davis 
wrote:
 On Wednesday, June 19, 2019 12:28:12 PM MDT XavierAP via 
 Digitalmars-d-learn wrote:
 [...]
The DIPs are here: https://github.com/dlang/DIPs [...]
DIP1014 has not been implemented in DMD or druntime yet, AFAIK
Jun 19 2019
parent Les De Ridder <les lesderid.net> writes:
On Wednesday, 19 June 2019 at 20:18:58 UTC, Max Haughton wrote:
 On Wednesday, 19 June 2019 at 19:25:59 UTC, Jonathan M Davis 
 wrote:
 On Wednesday, June 19, 2019 12:28:12 PM MDT XavierAP via 
 Digitalmars-d-learn wrote:
 [...]
The DIPs are here: https://github.com/dlang/DIPs [...]
DIP1014 has not been implemented in DMD or druntime yet, AFAIK
I have two open PRs for the druntime[1] and Phobos[2] implementations, but no dmd yet. [1] https://github.com/dlang/druntime/pull/2638 [2] https://github.com/dlang/phobos/pull/7075
Jun 19 2019
prev sibling next sibling parent reply XavierAP <n3minis-git yahoo.es> writes:
Hmmm I know about move semantics, and C++11 etc. I just don't 
know how related all that is to my original question. :)

On Wednesday, 19 June 2019 at 19:25:59 UTC, Jonathan M Davis 
wrote:
 though if I understand correctly with RVO, it may just place 
 the return value outside of the function in the first place to 
 avoid needing to move.
Indeed, unrelated: https://dlang.org/glossary.html#nrvo
 func(foo(), bar(42), baz("hello"));

 assuming that none of these functions return by ref, func is 
 taking temporaries from several functions, and the spec 
 actually guarantees that they will not be copied.
Where does the spec say this?? (This case is actually my question.) However, please understand that naive moving is not an answer for me. Moving is still work! It would still be more efficient if foo's parameters were references/pointers -- if D functions were able to bind rvalues as such. Theoretically a compiler could optimize by realizing that if a value parameter is not modified by the function (and it doesn't fit in a register etc), it can be read at its original location/address in the caller's stack, i.e. by reference/pointer. Again, nothing to do with moving. But I really doubt the D compilers do this, first because C++ probably don't, or else all C++ programmers are wasting their fingers and screen real state typing const & zillions of times; and second because D would not be able to bind as ref in case the argument happened to be an rvalue. __________ OK so I try to experiment myself: /**********/ struct XY { int x, y; ~this() { writeln("Destroyed!"); } } int sum(XY p) { return p.x + p.y; } void main() { XY p; p.sum; } /**********/ Destroyed! Destroyed! Note that the compiler didn't realize that p isn't needed after the last statement of main, as you thought. /**********/ XY get() { XY p; return p; } void main() { get.sum; } /**********/ Destroyed! Now with an rvalue returned from get, interesting, no copy. Still, I wonder what really happened. Again, moving between stacks would still be work. And a different optimization can explain this non copy, for example inlining. __________ Again, does the spec really mention any of this moving or eliding? I have found nothing.
Jun 19 2019
parent reply XavierAP <n3minis-git yahoo.es> writes:
On Wednesday, 19 June 2019 at 21:06:48 UTC, XavierAP wrote:
 Now with an rvalue returned from get, interesting, no copy. 
 Still, I wonder what really happened. Again, moving between 
 stacks would still be work. And a different optimization can 
 explain this non copy, for example inlining.
My guess as to what may be happening (I've never used a disassembler and I wasn't planning on starting today yet) is simple. The rvalue returned by get() is possibly not popped out from the stack, but rather left in the same place as sum() is called on it. This is actually optimum, but hardly an optimization, rather it's the easiest and least effort for the compiler. Again this would mean no moving -- which is good, because moving is work. And also, this doesn't work in the general case. If parameters are by value, everything works perfect when I pass rvalues (but we already knew that, not answering my question); however if I pass lvalues they will be copied every time. So my question is open... what about const ref rvalue parameters? Or another possible optimization: in case the function is declared with const parameters (not ref), lvalues can be accessed by ref as an optimization; and rvalues can use the same trick they do now. As a consequence, no unnecessary copies ever -- thanks to const. This is even better because I don't have to type so many ref, which were never part of the algorithm but baby instructions for the dumb compiler (const & everywhere is one of the ugliest and most annoying things in C++).
Jun 19 2019
parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Wednesday, June 19, 2019 4:45:04 PM MDT XavierAP via Digitalmars-d-learn 
wrote:
 On Wednesday, 19 June 2019 at 21:06:48 UTC, XavierAP wrote:
 Now with an rvalue returned from get, interesting, no copy.
 Still, I wonder what really happened. Again, moving between
 stacks would still be work. And a different optimization can
 explain this non copy, for example inlining.
My guess as to what may be happening (I've never used a disassembler and I wasn't planning on starting today yet) is simple. The rvalue returned by get() is possibly not popped out from the stack, but rather left in the same place as sum() is called on it. This is actually optimum, but hardly an optimization, rather it's the easiest and least effort for the compiler. Again this would mean no moving -- which is good, because moving is work. And also, this doesn't work in the general case. If parameters are by value, everything works perfect when I pass rvalues (but we already knew that, not answering my question); however if I pass lvalues they will be copied every time. So my question is open... what about const ref rvalue parameters? Or another possible optimization: in case the function is declared with const parameters (not ref), lvalues can be accessed by ref as an optimization; and rvalues can use the same trick they do now. As a consequence, no unnecessary copies ever -- thanks to const. This is even better because I don't have to type so many ref, which were never part of the algorithm but baby instructions for the dumb compiler (const & everywhere is one of the ugliest and most annoying things in C++).
If you want to know the current state of passing rvalues by ref, watch Andrei's talk. If you've already watched it, then you know what the current plan is, and I don't understand what you're trying to find out. Until the DIP is implemented, if you want a function to accept lvalues by ref and still accept rvalues, then use auto ref. e.g. void foo(T)(auto ref T t) {...} The refness of the parameter will be inferred based on whether it's given an lvalue or rvalue. If a templated function doesn't fit the bill, then you have to overload the function based on ref. e.g. void foo(ref T t) {...} void foo(T t) {...} Either way, const has nothing to do with any of this. You're free to mark a ref or auto ref parameter as const, but it has nothing to do with whether rvalues are accepted, and it will never have anything to do with whether rvalues are accepted. D's const is far too restrictive for it to make any sense to base rvalue stuff on it like they do in C++. The DIP has nothing do with const and everything to do with ref. Regardless, the refness of a parameter is part of its type, and I'd be very surprised if it were ever changed so that any parameter that was not marked with ref was ever ref. Certainly, it wouldn't work for the refness to change based on the argument, because the refness is part of the function's type and determines how the generated code passes the argument. The closest that we have and likely ever will have to the same function accepting by ref and by value is auto ref, and that only works, because it's a template, and different versions of the function are generated based on what's passed. It's never the case that the exact same function takes some values by ref and some not. Even once ref parameters accept rvalues, they're going to have to be turned into lvalues underneath the hood for that to work. It just won't require you to do it explicitly anymore, and the lifetime of the generated lvalue will be the same as any temporary. Ultimately, if you want a function to accept both rvalues and lvalues as efficiently as posible, just templatize it and use auto ref. - Jonathan M Davis
Jun 19 2019
parent XavierAP <n3minis-git yahoo.es> writes:
On Thursday, 20 June 2019 at 00:30:35 UTC, Jonathan M Davis wrote:
 Ultimately, if you want a function to accept both rvalues and 
 lvalues as efficiently as posible, just templatize it and use 
 auto ref.
I'm aware of auto ref, and I've used it to solve this same problem when I had a template, but as you say it works with templates only, not plain non-templated functions.
 Or another possible optimization: in case the function is 
 declared with const parameters (not ref), lvalues can be 
 accessed by ref as an optimization; and rvalues can use the 
 same trick they do now. As a consequence, no unnecessary 
 copies ever -- thanks to const. This is even better because I 
 don't have to type so many ref, which were never part of the 
 algorithm but baby instructions for the dumb compiler (const & 
 everywhere is one of the ugliest and most annoying things in 
 C++).
Either way, const has nothing to do with any of this. You're free to mark a ref or auto ref parameter as const, but it has nothing to do with whether rvalues are accepted, and it will never have anything to do with whether rvalues are accepted. D's const is far too restrictive for it to make any sense to base rvalue stuff on it like they do in C++. The DIP has nothing do with const and everything to do with ref. [...] Regardless, the refness of a parameter is part of its type, and I'd be very surprised if it were ever changed so that any parameter that was not marked with ref was ever ref.
I know. That's why I look to the general solution to bind ref rvalues as a solution to bind const ref in particular. By the way it looks to me that most of the demand in the D community to bind to ref is for chain return ref functions. I wonder why no one in D is bothered about not being able to use const ref parameters easily, while in C++ everyone is bothered to do it, and passing a read-only struct/class by value won't pass by the least alert reviewer. My guess is that in D it's very often ranges that get passed, and these are passed as slices, which are by nature refs that don't reallocate, and can also be decorated const. Still the const ref concern stays more or less. Rust has a solution too (&) of course. My new idea about const only would be a compiler optimization, not part of the language/ABI. The same way as RVO, which under the hood (but not at the ABI level) is implemented as changing the function signature completely. This const optimization would not change the function's ABI either. However I see a practical problem to implement this optimization idea. RVO changes the function signature under the hood, but in the same way for every occurrence/call. This const optimization would need to change the signature, but only in the occurrences where the function is called with lvalues instead,not rvalues. So in practice turning every function with const struct parameters as a template; but under the hood, maintaining a single plan signature in the ABI. I wonder if this is as easy or feasible as RVO.
Jun 19 2019
prev sibling parent Nicholas Wilson <iamthewilsonator hotmail.com> writes:
On Wednesday, 19 June 2019 at 19:25:59 UTC, Jonathan M Davis 
wrote:

 Aside from looking through the newsgroup/forum for discussions 
 on DIPs, that's pretty much all you're going to find on that. 
 Andrei's talk is the most up-to-date information that we have 
 about this particular DIP.
The preliminary implementation is the most practicable up to date info there: https://github.com/dlang/dmd/pull/9817
Jun 20 2019