Thread overview
Parameter forwarding
Oct 14, 2021
Kostiantyn Tokar
Oct 14, 2021
Paul Backus
Oct 14, 2021
Tejas
Oct 15, 2021
Kostiantyn Tokar
Oct 15, 2021
Tejas
Oct 16, 2021
Kostiantyn Tokar
Oct 19, 2021
Imperatorn
Oct 15, 2021
Kostiantyn Tokar
October 14, 2021

Hello.

I'm concerned about D's move semantics and how Phobos supports it. For example, std.typecons.Tuple.

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.

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. 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?

only(A(0), A(1), A(2))
        .filter!(a => a.i == 1)
        .takeOne
        .front;

Code

So is there no way to utilize move semantics using Phobos? Why forwarding is so neglected?

October 14, 2021

On Thursday, 14 October 2021 at 16:53:17 UTC, Kostiantyn Tokar wrote:

>

Take a look at Tuple constructor.

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.

October 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:

>

[...]

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?

October 15, 2021

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.

October 15, 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:

>

Take a look at Tuple constructor.

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,

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?

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?

October 15, 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:

>

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

October 16, 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:

>

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.

October 19, 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:

>

[...]

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