www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Copying structs with pointers

reply Peter Alexander <peter.alexander.au gmail.com> writes:
Just came across this issue today. I wanted to create a typical value 
type wrapper around a heap allocated object. I even created a nice 
postblit constructor like a good D programmer.


struct Ptr
{
   private int* p;
   this(int x) { p = new int; *p = x; }
   this(this) { int x = *p; p = new int; *p = x; }
   ...
}

void main()
{
   const Ptr a = Ptr(1);
   Ptr b = a; // error
}


Problem is, this code is illegal because you can't create a non-const 
copy of a const value type that contains pointers or references (due to 
transitive const).

I can get around it in this particular instance by creating a dup 
property (like a C++ copy constructor):

struct Ptr
{
    property const Ptr dup()
   {
     return Ptr(*p);
   }
}

But it still means that my struct is non-copyable in general. For 
example, I can't fill a range with a const Ptr.

const Ptr a = Ptr(1);
Ptr[10] arr;
std.algorithm.fill(arr[], a); // error


I understand why the shallow copy isn't allowed in general (it would 
break transitive const), but as the dup property demonstrates, I am able 
to create a deep copy of Ptr without violating const-correctness -- the 
problem is that I am not able to express this using a postblit constructor.

Am I missing something? How are you supposed to define value semantics 
on structs containing pointers?
Jul 20 2011
next sibling parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On 2011-07-20 14:49, Peter Alexander wrote:
 Just came across this issue today. I wanted to create a typical value
 type wrapper around a heap allocated object. I even created a nice
 postblit constructor like a good D programmer.
 
 
 struct Ptr
 {
 private int* p;
 this(int x) { p = new int; *p = x; }
 this(this) { int x = *p; p = new int; *p = x; }
 ...
 }
 
 void main()
 {
 const Ptr a = Ptr(1);
 Ptr b = a; // error
 }
 
 
 Problem is, this code is illegal because you can't create a non-const
 copy of a const value type that contains pointers or references (due to
 transitive const).
 
 I can get around it in this particular instance by creating a dup
 property (like a C++ copy constructor):
 
 struct Ptr
 {
  property const Ptr dup()
 {
 return Ptr(*p);
 }
 }
 
 But it still means that my struct is non-copyable in general. For
 example, I can't fill a range with a const Ptr.
 
 const Ptr a = Ptr(1);
 Ptr[10] arr;
 std.algorithm.fill(arr[], a); // error
 
 
 I understand why the shallow copy isn't allowed in general (it would
 break transitive const), but as the dup property demonstrates, I am able
 to create a deep copy of Ptr without violating const-correctness -- the
 problem is that I am not able to express this using a postblit constructor.
 
 Am I missing something? How are you supposed to define value semantics
 on structs containing pointers?

Postblit doesn't work with const or immutable right now: http://d.puremagic.com/issues/show_bug.cgi?id=4867 - Jonathan M Davis
Jul 20 2011
parent reply Peter Alexander <peter.alexander.au gmail.com> writes:
On 20/07/11 11:06 PM, Jonathan M Davis wrote:
 Postblit doesn't work with const or immutable right now:
 http://d.puremagic.com/issues/show_bug.cgi?id=4867

Is that the issue though? I believe it's a deeper issue. An example: struct Ptr { int* p; this(int x) { p = new int; *p = x; } this(this) { /+ do nothing +/ } // (!!!) const int get() { return *p; } void set(int x) { *p = x; } } void main() { const Ptr a = Ptr(1); Ptr b = a; // shallow copy! const removed! b.set(2); assert(a.get() == 1); // FAIL } If you allow this then it means that you can get a non-const pointer through a const object, which breaks the transitivity of const.
Jul 20 2011
parent reply Peter Alexander <peter.alexander.au gmail.com> writes:
On 20/07/11 11:50 PM, Jonathan M Davis wrote:
 Maybe we actually need a copy constructor of some kind for this sort of case.
 I don't see how to get around it with a postblit. This is definitely a big
 problem.

 - Jonathan M Davis

My thoughts exactly.
Jul 20 2011
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 7/20/11 6:12 PM, Peter Alexander wrote:
 On 20/07/11 11:50 PM, Jonathan M Davis wrote:
 Maybe we actually need a copy constructor of some kind for this sort
 of case.
 I don't see how to get around it with a postblit. This is definitely a
 big
 problem.

 - Jonathan M Davis

My thoughts exactly.

Walter and I are on it and know what's to be done there. But we couldn't find the time to do it yet. Andrei
Jul 20 2011
prev sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On 2011-07-20 15:30, Peter Alexander wrote:
 On 20/07/11 11:06 PM, Jonathan M Davis wrote:
 Postblit doesn't work with const or immutable right now:
 http://d.puremagic.com/issues/show_bug.cgi?id=4867

Is that the issue though? I believe it's a deeper issue.

It means that regardless of whether there's a deeper issue, using const with postblit isn't going to work.
 An example:
 
 struct Ptr
 {
 int* p;
 this(int x) { p = new int; *p = x; }
 this(this) { /+ do nothing +/ } // (!!!)
 const int get() { return *p; }
 void set(int x) { *p = x; }
 }
 
 void main()
 {
 const Ptr a = Ptr(1);
 Ptr b = a; // shallow copy! const removed!
 b.set(2);
 assert(a.get() == 1); // FAIL
 }
 
 
 If you allow this then it means that you can get a non-const pointer
 through a const object, which breaks the transitivity of const.

Hmmm. This is definitely a problem. It should be possible to use postblits when the object being copied is const and has pointers and/or references. If not, that's fairly crippling. But without doing some sort of flow analysis, I don't see how the problem that you present can be fixed. If it were a copy constructor, it would be straightforward - everything gets default initialized and then you assign stuff from the const original, making copies where required. But a postblit does a shallow copy _first_, which means that either this is forced to point to const (thereby making it so that you can't do anything but a shallow copy and that the copy must be const), or the type system gets twisted a bit by allowing this to point to mutable even though it's const and the compiler then must ensure that copies are made (or at least that the original values are not kept) or const would be bypassed (and even that probably wouldn't work, since then you'd have to worry about the member variables being passed to functions as mutable inside of the constructor when they're really const underneath). And I don't think that either of those solutions really works. Maybe we actually need a copy constructor of some kind for this sort of case. I don't see how to get around it with a postblit. This is definitely a big problem. - Jonathan M Davis
Jul 20 2011