Thread overview
forward tuple arg to local variable + dtor
Jan 22, 2022
vit
Jan 22, 2022
Adam Ruppe
Jan 22, 2022
vit
Jan 22, 2022
Ali Çehreli
Jan 22, 2022
vit
Jan 22, 2022
Stanislav Blinov
Jan 22, 2022
vit
Jan 22, 2022
Stanislav Blinov
January 22, 2022

Hello,

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

Code:

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
January 22, 2022

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.

January 22, 2022

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?

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?
January 22, 2022
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

January 22, 2022

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:

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);
            }
        }
    }



January 22, 2022

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:

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:

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
    }
}
January 22, 2022

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.

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));
}
January 22, 2022

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:

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()).