www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - forward tuple arg to local variable + dtor

reply vit <vit vit.vit> writes:
Hello,

Why is tuple variable `params` immediately destructed after its 
construction?
Why is `check(-2)` after `dtor(2)`?

Code:
```d
import std.stdio : writeln;
import core.lifetime : forward, move;

struct Foo{
	int i;

     this(int i){
     	this.i = i;
         writeln("ctor(", i, "): ", cast(void*)&this);
     }

     ~this(){
         writeln("dtor(", i, "): ", cast(void*)&this);
         i *= -1;
     }
}

void seq(Args...)(auto ref Args args){
     Args params = forward!args;
     writeln("params initialized");

     alias foo = params[0];
     writeln("check(", foo.i, "): ", cast(void*)&foo);

}

void main(){
     writeln("Foo(1):");
     {
     	auto foo = Foo(1);
         writeln("check(", foo.i, "): ", cast(void*)&foo);
     }

     writeln("\nFoo(2):");
     seq(Foo(2));
}


```

Output:
```
Foo(1):
ctor(1): 7FFEBCAF0538
check(1): 7FFEBCAF0538
dtor(1): 7FFEBCAF0538

Foo(2):
ctor(2): 7FFEBCAF0548
dtor(2): 7FFEBCAF04F0
params initialized
check(-2): 7FFEBCAF04F0
dtor(0): 7FFEBCAF0558
```
Jan 22 2022
parent reply Adam Ruppe <destructionator gmail.com> writes:
You can't forward to a local variable. Local variables will be a 
copy of the tuple. forward only actually works if sent *directly* 
to another function call.

There's a bunch of things in D that only work in function 
parameter lists and not local variables. This is one of them.
Jan 22 2022
parent reply vit <vit vit.vit> writes:
On Saturday, 22 January 2022 at 14:23:32 UTC, Adam Ruppe wrote:
 You can't forward to a local variable. Local variables will be 
 a copy of the tuple. forward only actually works if sent 
 *directly* to another function call.

 There's a bunch of things in D that only work in function 
 parameter lists and not local variables. This is one of them.
Thanks, Why local variable of type tuple call destructors immediately after initialization? ```d import std.stdio : writeln; import std.meta : AliasSeq; struct Foo{ int i; this(int i){ this.i = i; writeln("ctor(", i, "): ", cast(void*)&this); } ~this(){ writeln("dtor(", i, "): ", cast(void*)&this); i *= -1; } } void main(){ { AliasSeq!(Foo, Foo) tup; static foreach(alias x; tup) writeln("check(", x.i, "): ", cast(void*)&x); //x is destructed } } ``` Print: ``` dtor(0): 7FFF30D76868 dtor(0): 7FFF30D76858 check(0): 7FFF30D76858 //dangling? check(0): 7FFF30D76868 //dangling? ```
Jan 22 2022
parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 1/22/22 07:17, vit wrote:

 Why local variable of type tuple call destructors immediately after
 initialization?
I don't even understand where the local variable comes from. If you want a pair of Foo objects, I would use std.typeconst.Tuple. Otherwise I would use AliasSeq as I understand it like the following: alias tup = AliasSeq!(Foo, Foo); static foreach(Type; tup) {{ Type x; writeln("check(", x.i, "): ", cast(void*)&x); }} So, to me, AliasSeq!(Foo, Foo) is just a pair of types. I instantiate an object for each type individually and use it inside. Ali
Jan 22 2022
parent reply vit <vit vit.vit> writes:
On Saturday, 22 January 2022 at 17:23:12 UTC, Ali Çehreli wrote:
 On 1/22/22 07:17, vit wrote:

 Why local variable of type tuple call destructors immediately
after
 initialization?
I don't even understand where the local variable comes from. If you want a pair of Foo objects, I would use std.typeconst.Tuple. Otherwise I would use AliasSeq as I understand it like the following: alias tup = AliasSeq!(Foo, Foo); static foreach(Type; tup) {{ Type x; writeln("check(", x.i, "): ", cast(void*)&x); }} So, to me, AliasSeq!(Foo, Foo) is just a pair of types. I instantiate an object for each type individually and use it inside. Ali
I want implement something like this: ```d import std.stdio : writeln; import std.meta : AliasSeq, staticMap; import core.memory : pureMalloc, pureFree; import core.lifetime : emplace, forward; void main() safe{ auto rc1 = RcPtr!int.make(1); //ref counted pointer long result = 0; //apply can be safe apply!((ref int a, ref long b){ rc1 = null; //apply has copy of rc1, a is not dangling reference result = a + b; })(rc1, RcPtr!long.make(2)); assert(result == 3); } // safe access to data of multiple ref counted objects: public auto apply(alias fn, Args...)(scope auto ref Args args){ Args params = forward!args; //copy lvalue and move rvalue args property auto ref elm(alias param)() trusted{ return param.get(); } return fn(staticMap!(elm, params)); } //simple implementation of ref counted pointer struct RcPtr(T){ private Payload* payload; private this(Payload* payload) safe{ this.payload = payload; } //copy ctor public this(ref typeof(this) rhs){ this.payload = rhs.payload; if(payload) payload.count += 1; } //make data public static auto make(Args...)(auto ref Args args){ Payload* payload = () trusted{ return cast(Payload*)pureMalloc(Payload.sizeof); }(); emplace(payload, forward!args); return RcPtr(payload); } public ~this(){ this.opAssign(null); } //release payload void opAssign(typeof(null) nil){ if(payload){ payload.count -= 1; if(payload.count == 0){ destroy(*payload); () trusted{ pureFree(payload); }(); payload = null; } } } // ref T get() system{ assert(payload); return payload.data; } private struct Payload{ int count = 1; T data; this(Args...)(auto ref Args args){ data = T(forward!args); } } } ```
Jan 22 2022
next sibling parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Saturday, 22 January 2022 at 18:00:58 UTC, vit wrote:

 I want implement something like this:
 ...
Take by value and make a copy without forwarding: ```d import std.typecons : Tuple; import std.meta : allSatisfy; enum bool isRcPtr(T) = is(T == RcPtr!U, U); // safe access to data of multiple ref counted objects: public auto apply(alias fn, Args...)(Args args) if (allSatisfy!(isRcPtr, Args)) { Tuple!Args params = args; // borrow all property auto ref elm(alias param)() trusted{ return param.get(); } return fn(staticMap!(elm, args)); // staticMap over original args tuple } ``` Or make a helper function that takes by value and forward to that: ```d template apply(alias fn) { private auto impl(Args...)(Args args) { property auto ref elm(alias param)() trusted{ return param.get(); } return fn(staticMap!(elm, args)); } auto apply(Args...)(auto ref Args args) if (allSatisfy!(isRcPtr, Args)) { import core.lifetime : forward; return impl(forward!args); // impl takes by value - RcPtrs passed by reference will be copied } } ```
Jan 22 2022
parent vit <vit vit.vit> writes:
On Saturday, 22 January 2022 at 19:01:09 UTC, Stanislav Blinov 
wrote:
 On Saturday, 22 January 2022 at 18:00:58 UTC, vit wrote:

 [...]
Take by value and make a copy without forwarding: ```d import std.typecons : Tuple; import std.meta : allSatisfy; [...]
Thanks, second options is what I need. (In my case forwarding is more complex because weak ptrs need lock()).
Jan 22 2022
prev sibling parent Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Saturday, 22 January 2022 at 18:00:58 UTC, vit wrote:

 I want implement something like this:
Scratch the previous reply, 'twas a brain fart... Simply take by value, no need for extra copies at all in that case. Arguments themselves will become those copies as needed. ```d import std.meta : allSatisfy; enum bool isRcPtr(T) = is(T == RcPtr!U, U); // safe access to data of multiple ref counted objects: public auto apply(alias fn, Args...)(Args args) if (allSatisfy!(isRcPtr, Args)) { property auto ref elm(alias param)() trusted{ return param.get(); } return fn(staticMap!(elm, args)); } ```
Jan 22 2022