Thread overview
smart pointer for interior pointers
May 28, 2015
Artur Skawina
May 28, 2015
ketmar
May 27, 2015
I just was working on a project, and I had a need for two members of a struct to share state.

How could this work? A simple example:

struct S
{
   int x;
   int *y; // should refer to x
   this(int v)
   {
      x = v;
      y = &x;
   }
}

But this doesn't work on copying, because when you copy S, S.y is still pointing at the OLD S. Example:


S s1 = S(5);
S s2 = s1;
s1.x = 4;
writeln(*s2.y); // oops, writes 4, because s2.y is pointing at s1.x, not s2.x

But we can solve this with a postblit:

this(this) { y = &x;}

Although this works, there are issues:

1. y's target is defined by the type, it's impossible to rebind y at runtime, without having a copy mess that up.
2. Copying still requires executing a function to fixup the pointer.
3. Even if y's relative target isn't supposed to change, S has to know where to point y at relative to itself! For example, if S was inside a struct like this:

struct S2
{
   S s;
   int y; // I really want s.x to point at *this* y
}

Now, you need to add a postblit to S2 as well.

But with alias this, we can define a way to solve all these problems.

struct SPtr(T)
{
    ptrdiff_t _offset;
    void opAssign(T *orig) { _offset = cast(void *)orig - cast(void *)&this;}
    inout(T) *_get() inout { return cast(inout(T)*)((cast(inout(void) *)&this) + _offset);}
    alias _get this;
}

Basically, instead of storing the pointer, we store the offset to the struct itself. This works as long as the SPtr instance stays co-located with its target.

It has some advantages:

1. It can be defined at compile/initialization time:
struct S
{
   int x;
   SPtr!int y = SPtr(offsetFromytox); // not sure of an easy way to autocreate the offset :)
}

2. You can copy S anywhere, and every instance's y will point at the instance's x, without running any fixup code. You can even use memcpy.

The disadvantage, and I think there's only one, is that using SPtr must perform math that a straight pointer will not. I think in cases where this is needed, I prefer this disadvantage over the ones of other choices.

Thoughts? Does this seem like a good candidate for std.typecons?

If so, what's a good name? I want something kind of short :)

-Steve
May 28, 2015
On 05/28/15 01:31, Steven Schveighoffer via Digitalmars-d wrote:
> I just was working on a project, and I had a need for two members of a struct to share state.
> 
> How could this work? A simple example:
> 
> struct S
> {
>    int x;
>    int *y; // should refer to x
>    this(int v)
>    {
>       x = v;
>       y = &x;
>    }
> }
> 
> But this doesn't work on copying, because when you copy S, S.y is still pointing at the OLD S. Example:
> 
> 
> S s1 = S(5);
> S s2 = s1;
> s1.x = 4;
> writeln(*s2.y); // oops, writes 4, because s2.y is pointing at s1.x, not s2.x
> 
> But we can solve this with a postblit:
> 
> this(this) { y = &x;}
> 
> Although this works, there are issues:

It doesn't.

   S f() { return S(42); }


> 1. y's target is defined by the type, it's impossible to rebind y at runtime, without having a copy mess that up.
> 2. Copying still requires executing a function to fixup the pointer.
> 3. Even if y's relative target isn't supposed to change, S has to know where to point y at relative to itself! For example, if S was inside a struct like this:
> 
> struct S2
> {
>    S s;
>    int y; // I really want s.x to point at *this* y
> }
> 
> Now, you need to add a postblit to S2 as well.
> 
> But with alias this, we can define a way to solve all these problems.
> 
> struct SPtr(T)
> {
>     ptrdiff_t _offset;
>     void opAssign(T *orig) { _offset = cast(void *)orig - cast(void *)&this;}
>     inout(T) *_get() inout { return cast(inout(T)*)((cast(inout(void) *)&this) + _offset);}
>     alias _get this;
> }
> 
> Basically, instead of storing the pointer, we store the offset to the struct itself. This works as long as the SPtr instance stays co-located with its target.

  auto a = s.y;
  // this 'a' now implicitly converts to 'int', but...

  void g(T)(T a);
  g(s.y);         // ditto.


artur
May 28, 2015
On Wed, 27 May 2015 17:31:32 -0600, Steven Schveighoffer wrote:

> But we can solve this with a postblit:

seems that you forgot about "move" semantics for structs. under some conditions struct can be "moved", not "copied", so it `memcpy`ed and no postblit will be called.

May 28, 2015
On 5/27/15 6:21 PM, Artur Skawina via Digitalmars-d wrote:

>> But with alias this, we can define a way to solve all these problems.
>>
>> struct SPtr(T)
>> {
>>      ptrdiff_t _offset;
>>      void opAssign(T *orig) { _offset = cast(void *)orig - cast(void *)&this;}
>>      inout(T) *_get() inout { return cast(inout(T)*)((cast(inout(void) *)&this) + _offset);}
>>      alias _get this;
>> }
>>
>> Basically, instead of storing the pointer, we store the offset to the struct itself. This works as long as the SPtr instance stays co-located with its target.
>
>    auto a = s.y;
>    // this 'a' now implicitly converts to 'int', but...
>
>    void g(T)(T a);
>    g(s.y);         // ditto.
>

Yes, both your cases (including the one that I didn't quote) show that such constructs must be controlled privately. For example, S should really be written like this:

struct S
{
  int x;
  private SPtr!int _y;
  int *y() {return _y;}
  void y(int * newy) { _y = newy; }
}

And it gets kind of sticky from there if you wanted to replace an actual variable :) For example, y++.

But you can get most of the abilities of a member and still not destroy the semantic of having it reference the copy.

I actually need this in the project I'm writing, which I'm hoping to get into Phobos, and I'm either going to define it in that project, or define it in std.typecons. Maybe the best thing to do is to define it privately for that module, and then move it somewhere more public if it turns out to be something that's useful elsewhere.

-Steve
May 28, 2015
On 5/27/15 9:13 PM, ketmar wrote:
> On Wed, 27 May 2015 17:31:32 -0600, Steven Schveighoffer wrote:
>
>> But we can solve this with a postblit:
>
> seems that you forgot about "move" semantics for structs. under some
> conditions struct can be "moved", not "copied", so it `memcpy`ed and no
> postblit will be called.
>

Right, that is a drawback of the first form. You have to ensure that doesn't happen (which is why we say struct interior pointers are illegal).

The real proposal doesn't have that issue, and that's actually the point of it :)

-Steve