www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Parameter forwarding

reply Kostiantyn Tokar <tokarkonstantyn yandex.ua> writes:
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
parent reply Paul Backus <snarwin gmail.com> writes:
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
next sibling parent reply Tejas <notrealemail gmail.com> writes:
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:
 [...]
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.
Maybe DIP 1040 will automatically solve that?
last use of objects that is a copy gets elided into a move
That is what it states, so maybe this one time, the problem will go away just by waiting?
Oct 14 2021
parent reply Kostiantyn Tokar <tokarkonstantyn yandex.ua> writes:
On Thursday, 14 October 2021 at 17:53:57 UTC, Tejas wrote:
 Maybe DIP 1040 will automatically solve that?

last use of objects that is a copy gets elided into a move
That is what it states, so maybe this one time, the problem will go away just by waiting?
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.
Oct 14 2021
parent reply Tejas <notrealemail gmail.com> writes:
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:
 Maybe DIP 1040 will automatically solve that?

last use of objects that is a copy gets elided into a move
That is what it states, so maybe this one time, the problem will go away just by waiting?
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.
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 default
Oct 15 2021
parent reply Kostiantyn Tokar <tokarkonstantyn yandex.ua> writes:
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:
 On Thursday, 14 October 2021 at 17:53:57 UTC, Tejas wrote:
 Maybe DIP 1040 will automatically solve that?

last use of objects that is a copy gets elided into a move
That is what it states, so maybe this one time, the problem will go away just by waiting?
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.
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 default
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 16 2021
parent Imperatorn <johan_forsberg_86 hotmail.com> writes:
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:
 [...]
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.
I think it's very likely 1040 will be accepted
Oct 19 2021
prev sibling parent Kostiantyn Tokar <tokarkonstantyn yandex.ua> writes:
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:
 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.
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?
Oct 15 2021