www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Temporaries / struct RAII patterns

reply Witold <witold.baryluk+dlang gmail.com> writes:
Not sure how to name the problem, but here it is.

I want to create a struct instance on a stack using a helper 
function, manipulate it Fluent-like style, then destructor be 
invoked, that will do something with its state.

I do not want to use `Unique`, because that will allocate actual 
data on heap. And unique will have pointer to it. This is because 
one can do release and move on Unique. I do not need (or want) 
release or move.

I want something that can be done on a stack, but can be 
manipulated and passed to functions, and destructor called once I 
either go out of scope, or it is not used at all, and expression 
/ statement ends.


```d
struct X {
   string message;
   string file;
   int line;
   string[string] kvs;

   ref X F(string fmt, Args...)(const Args args) {
     return this;
     ...
   }
   ref X KV(string k, string v) {
     kvs[k] = v;
     return this;
   }
   ~this() {
      DoSomething(&this, message, kvs);
   }
}

X Foo(const string message, string file = __FILE__, int line = 
__LINE__) {
   return X(message, file, line)
}

X Foo(string fmt, Args...)(lazy Args args, string file = 
__FILE__, int line = __LINE__) {
   return X(message, file, line).F!(fmt, Args)(args);
}


void main() {
    Foo("a");
    Foo("b").kv("k1", "v1").kv("k2", "v2");
    Foo!"c %d %s"(1, "ble").kv("k3", "v3");
}
```


I know I can do this:

```d
void main() {
    {
      scope x = Foo("a");
    }
    {
      scope x = Foo("b");
      x.KV("k1", "v1");
      x.KV("k2", "v2");
    }
}
```

But this is not going to scale (I will have many of these 
statements in each file and function), and now requires me to 
remember to put `scope` in front of each variable (`scope` 
constraint keyword on struct type itself is deprecated), which is 
error prone.

How do I ensure that `X` is not copied (i.e. on return from `f`), 
or when doing `kvs` (and similar functions), and that `X` is 
allocated on the stack, and is destroyed properly.

I also do want to be able to d the second form (with explicit 
scope and calls to `kv`, i.e. when doing `kv` calls in a loop 
from some other array or associative array).


I cannot use `ref` return on the `Foo`, because it the thing I am 
returning either is not an lvalue, or any lvalue it could 
reference would be on stack of `Foo`, so it will be returning 
invalid reference after `Foo` returns.

Cheers.
Oct 15 2023
next sibling parent Witold <witold.baryluk+dlang gmail.com> writes:
Also I added

```d
        // Disables default construction
         disable this();
        // Disable copy constructor
         disable this(ref X);

```

to `struct X`,

and that triggers an error, because compiler is trying to call a 
copy constructor when doing return from `Foo`.
Oct 15 2023
prev sibling parent Witold <witold.baryluk+dlang gmail.com> writes:
So it looks like to ensure RVO (return value optimization), I 
need to write it like this:

```d
X Foo(string fmt, Args...)(lazy Args args, string file = 
__FILE__, int line = __LINE__) {
   auto r = X(message, file, line);
   r.F!(fmt, Args)(args);
   return r;
}
```

Instead of:

```d
X Foo(string fmt, Args...)(lazy Args args, string file = 
__FILE__, int line = __LINE__) {
   return X(message, file, line).F!(fmt, Args)(args);
}
```


Otherwise copy constructor is called. In this case I can emulate 
"move" semantic using copy constructor:

```d
struct X {
    disable this();
   this(ref X other) {
     message = other.message;
     file = other.file;
     line = other.line;
     kvs = other.kvs;

     // mark other moved out
     other.message = null;
     other.file = null;
     other.line = -1;
     other.kvs = null;
   }

   // ...
   ~this() {
     // Only do action if it was not moved out by "copy" 
constructor
     if (message !is null) {
       DoSomething(&this, message, kvs);
     }
   }
}
```


But it is so so.

I do not plan to expose `X` type directly, so nobody but the 
library should be creating it, but user can technically assign it 
to variables (there are reasons for this), for future 
manipulation.
Oct 16 2023