digitalmars.D.learn - Parameter forwarding
- Kostiantyn Tokar (47/47) Oct 14 2021 Hello.
- Paul Backus (10/23) Oct 14 2021 Poor test coverage. If you go through Phobos and test everything
- Tejas (4/18) Oct 14 2021 That is what it states, so maybe this one time, the problem will
- Kostiantyn Tokar (9/13) Oct 14 2021 It would be great, but still, if I understand correctly, it is
- Tejas (7/23) Oct 15 2021 Yeah blitting as a concept will go away from D(will only stay for
- Kostiantyn Tokar (7/32) Oct 16 2021 Now I get it. If this DIP will be accepted, I just have to
- Imperatorn (3/12) Oct 19 2021 I think it's very likely 1040 will be accepted
- Kostiantyn Tokar (34/58) Oct 15 2021 I see. So there should be forwarding, but it is not implemented.
Hello. I'm concerned about D's move semantics and how Phobos supports it. For example, `std.typecons.Tuple`. ```d struct A { int i; this(this) { writeln("Expensive copy"); } } void main() { auto t = Tuple!(A)(A(42)); } ``` This code produces 1 unnecessary copy. Argument is an rvalue and can be passed by move. Indeed, one copy is elided when rvalue argument binds to constructor parameter, but then it became an lvalue. Take a look at `Tuple` [constructor](https://github.com/dlang/phobos/blob/4130a1176cdb6111b0c26c7c53702e10011ff067/std/typecons.d#L672). ```d this(Types values) { field[] = values[]; } ``` Actual fields are constructed from lvalues. Why there is no `auto ref` and `forward`? It looks like there is no way to construct a tuple without copying. But it gets even worse, because factory function `tuple` creates another level of indirection and produces 2 copies from an rvalue. C++'s tuples perform perfect forwarding. If class implements a move constructor, then neither tuple's constructor nor `make_tuple` produce copies. [Example](https://onlinegdb.com/sz0NhNNf2). Price for that is separate move constructor, but at least there is a way to avoid copying. But this pattern is common in Phobos. This UFCS chain produces 17 copies. But should it? ```d only(A(0), A(1), A(2)) .filter!(a => a.i == 1) .takeOne .front; ``` [Code](https://run.dlang.io/is/thEFC2) So is there no way to utilize move semantics using Phobos? Why forwarding is so neglected?
Oct 14 2021
On Thursday, 14 October 2021 at 16:53:17 UTC, Kostiantyn Tokar wrote:Take a look at `Tuple` [constructor](https://github.com/dlang/phobos/blob/4130a1176cdb6111b0c26c7c53702e10011ff067/std/typecons.d#L672). ```d this(Types values) { field[] = values[]; } ``` Actual fields are constructed from lvalues. Why there is no `auto ref` and `forward`? It looks like there is no way to construct a tuple without copying.Poor test coverage. If you go through Phobos and test everything with a non-copyable struct (` disable this(this)`), you will find that most templates (including `Tuple`) fail to compile. It is simply a case that the authors of the code never considered.So is there no way to utilize move semantics using Phobos? Why forwarding is so neglected?I suspect it is neglected because nobody wants to volunteer for the tedious work of going through Phobos, adding unit tests that check for unnecessary copies, and inserting `auto ref` and `forward` in the appropriate places.
Oct 14 2021
On Thursday, 14 October 2021 at 17:37:21 UTC, Paul Backus wrote:On Thursday, 14 October 2021 at 16:53:17 UTC, Kostiantyn Tokar wrote:Maybe DIP 1040 will automatically solve that?[...]Poor test coverage. If you go through Phobos and test everything with a non-copyable struct (` disable this(this)`), you will find that most templates (including `Tuple`) fail to compile. It is simply a case that the authors of the code never considered.[...]I suspect it is neglected because nobody wants to volunteer for the tedious work of going through Phobos, adding unit tests that check for unnecessary copies, and inserting `auto ref` and `forward` in the appropriate places.last use of objects that is a copy gets elided into a moveThat is what it states, so maybe this one time, the problem will go away just by waiting?
Oct 14 2021
On Thursday, 14 October 2021 at 17:53:57 UTC, Tejas wrote:Maybe DIP 1040 will automatically solve that?It would be great, but still, if I understand correctly, it is not perfect solution for lvalue arguments. Consider, e.g., `Tuple` construction from an lvalue. With this DIP and current state of constructor it will be 1 copy (from argument to constructor's parameter) and 1 blit (from parameter to the field). With `auto ref` and forwarding it would be 1 copy from parameter to the field and no blits. Forwarding saves one blit per indirection, i.e., for `tuple` it would be 2 blits vs. 0.last use of objects that is a copy gets elided into a moveThat is what it states, so maybe this one time, the problem will go away just by waiting?
Oct 14 2021
On Friday, 15 October 2021 at 06:52:41 UTC, Kostiantyn Tokar wrote:On Thursday, 14 October 2021 at 17:53:57 UTC, Tejas wrote:Yeah blitting as a concept will go away from D(will only stay for backwards-compatibility) Copy constructors already made blits obselete, move constructors will remove the need for post-blits and finish the job and the second case that you talked about will become the defaultMaybe DIP 1040 will automatically solve that?It would be great, but still, if I understand correctly, it is not perfect solution for lvalue arguments. Consider, e.g., `Tuple` construction from an lvalue. With this DIP and current state of constructor it will be 1 copy (from argument to constructor's parameter) and 1 blit (from parameter to the field). With `auto ref` and forwarding it would be 1 copy from parameter to the field and no blits. Forwarding saves one blit per indirection, i.e., for `tuple` it would be 2 blits vs. 0.last use of objects that is a copy gets elided into a moveThat is what it states, so maybe this one time, the problem will go away just by waiting?
Oct 15 2021
On Friday, 15 October 2021 at 17:22:52 UTC, Tejas wrote:On Friday, 15 October 2021 at 06:52:41 UTC, Kostiantyn Tokar wrote:Now I get it. If this DIP will be accepted, I just have to provide move constructor/assignment for my types and forwarding would work. It sounds really great. If so, there is no need to rewrite half of Phobos to support forwarding. And it even covers last use of a field of an aggregate! Hope it will be accepted. Paul, Tejas, thank you for answers.On Thursday, 14 October 2021 at 17:53:57 UTC, Tejas wrote:Yeah blitting as a concept will go away from D(will only stay for backwards-compatibility) Copy constructors already made blits obselete, move constructors will remove the need for post-blits and finish the job and the second case that you talked about will become the defaultMaybe DIP 1040 will automatically solve that?It would be great, but still, if I understand correctly, it is not perfect solution for lvalue arguments. Consider, e.g., `Tuple` construction from an lvalue. With this DIP and current state of constructor it will be 1 copy (from argument to constructor's parameter) and 1 blit (from parameter to the field). With `auto ref` and forwarding it would be 1 copy from parameter to the field and no blits. Forwarding saves one blit per indirection, i.e., for `tuple` it would be 2 blits vs. 0.last use of objects that is a copy gets elided into a moveThat is what it states, so maybe this one time, the problem will go away just by waiting?
Oct 16 2021
On Saturday, 16 October 2021 at 19:39:14 UTC, Kostiantyn Tokar wrote:On Friday, 15 October 2021 at 17:22:52 UTC, Tejas wrote:I think it's very likely 1040 will be accepted[...]Now I get it. If this DIP will be accepted, I just have to provide move constructor/assignment for my types and forwarding would work. It sounds really great. If so, there is no need to rewrite half of Phobos to support forwarding. And it even covers last use of a field of an aggregate! Hope it will be accepted. Paul, Tejas, thank you for answers.
Oct 19 2021
On Thursday, 14 October 2021 at 17:37:21 UTC, Paul Backus wrote:On Thursday, 14 October 2021 at 16:53:17 UTC, Kostiantyn Tokar wrote:I see. So there should be forwarding, but it is not implemented. The problem I see is that there is no simple solution for forwarding members of an aggregate. For example, ```d void foo()(auto ref A a, auto ref B b) { pragma(msg, __traits(isRef, a)); pragma(msg, __traits(isRef, b)); } void bar(Tuple!(A, B) t) { foo(forward!t.expand); // true, true - no move } ``` Do I need something like `forward`, but for fields of a parameter? ```d template forwardMember(alias arg, string member) { static if (__traits(isRef, arg) || __traits(isOut, arg) || __traits(isLazy, arg) || !is(typeof(move(__traits(getMember, arg, member))))) property auto ref forwardMember(){ return __traits(getMember, arg, member); } else property auto forwardMember(){ return move(__traits(getMember, arg, member)); } } ``` `forwardMember` doesn't work for `Tuple.expand` though. I didn't tried to make it more general, maybe some mixins are required. Doesn't forwarding members separately breaks anything?Take a look at `Tuple` [constructor](https://github.com/dlang/phobos/blob/4130a1176cdb6111b0c26c7c53702e10011ff067/std/typecons.d#L672). ```d this(Types values) { field[] = values[]; } ``` Actual fields are constructed from lvalues. Why there is no `auto ref` and `forward`? It looks like there is no way to construct a tuple without copying.Poor test coverage. If you go through Phobos and test everything with a non-copyable struct (` disable this(this)`), you will find that most templates (including `Tuple`) fail to compile. It is simply a case that the authors of the code never considered.So is there no way to utilize move semantics using Phobos? Why forwarding is so neglected?I suspect it is neglected because nobody wants to volunteer for the tedious work of going through Phobos, adding unit tests that check for unnecessary copies, and inserting `auto ref` and `forward` in the appropriate places.
Oct 15 2021