www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Error about disabled constructor when there is a custom one

reply "Minas Mina" <minas_mina1990 hotmail.co.uk> writes:
I have this structure:

struct Scene
{
	Array!Surface objects;	// objects in the scene
	Array!Light lights;		// lights in the scene
	
	/*private*/ BVHNode root;			// root node of the BVH tree
	
	 disable this(); // disable the default constructor because 
space needs to be reserved for objects and lights
	
	this(size_t objectReserveSpace = 20, size_t lightReserveSpace = 
3)
	{
		objects.reserve(objectReserveSpace);
		lights.reserve(lightReserveSpace);
	}
}



auto scene = Scene(); // error about  disabled constructor

Yes, the default constructor is  disabled BUT I am not using that 
one. I want to use the other one - the custom constructor. I 
guess it signals an error because it has those defaults 
parameters. But shouldn't the compiler choose that one?
Jan 22 2013
next sibling parent reply Dmitry Olshansky <dmitry.olsh gmail.com> writes:
22-Jan-2013 22:45, Minas Mina пишет:
 I have this structure:

 struct Scene
 {
      Array!Surface objects;    // objects in the scene
      Array!Light lights;        // lights in the scene

      /*private*/ BVHNode root;            // root node of the BVH tree

       disable this(); // disable the default constructor because space
 needs to be reserved for objects and lights

      this(size_t objectReserveSpace = 20, size_t lightReserveSpace = 3)
      {
          objects.reserve(objectReserveSpace);
          lights.reserve(lightReserveSpace);
      }
 }



 auto scene = Scene(); // error about  disabled constructor

That is the reason I dislike D's struct constructors as they currently stand. Behold as the above is always rewritten to: auto scene = Scene.init; that is the same as Scene scene; And for Scene.init to work it has to have T.init for all of its members. You've hit what I think is the design bug w.r.t. conflating 0-argument constructor (including one with all default args) and T.init. Short answer is: use static opCall instead. In the meantime let us all pray some supreme gods so that 0-arg constructor support is introduced later on. (because opCall doesn't have some powers of constructors such as constructing const object)
 Yes, the default constructor is  disabled BUT I am not using that one. I
 want to use the other one - the custom constructor. I guess it signals
 an error because it has those defaults parameters. But shouldn't the
 compiler choose that one?

The official stance seems to be that the code shouldn't compile i.e. accepts-invalid. http://d.puremagic.com/issues/show_bug.cgi?id=3438 -- Dmitry Olshansky
Jan 22 2013
parent Dmitry Olshansky <dmitry.olsh gmail.com> writes:
23-Jan-2013 00:28, Minas Mina пишет:
  From Jonathan M Davis:

 "...At this point, I don't think that the situation with default
 constructors and
 structs is going to change. It's a result of requiring init properties
 for all
 types, and is thus a "forced fault" in the language..."

 Why does requiring init properties for all types results in this? What
 does that even mean?

I don't buy it either. 0-argument constructor have nothing to do with requiring .init. -- Dmitry Olshansky
Jan 22 2013
prev sibling next sibling parent reply "Simen Kjaeraas" <simen.kjaras gmail.com> writes:
On 2013-01-22, 19:45, Minas Mina wrote:

 I have this structure:

 struct Scene
 {
 	Array!Surface objects;	// objects in the scene
 	Array!Light lights;		// lights in the scene
 	
 	/*private*/ BVHNode root;			// root node of the BVH tree
 	
 	 disable this(); // disable the default constructor because space needs  
 to be reserved for objects and lights
 	
 	this(size_t objectReserveSpace = 20, size_t lightReserveSpace = 3)
 	{
 		objects.reserve(objectReserveSpace);
 		lights.reserve(lightReserveSpace);
 	}
 }



 auto scene = Scene(); // error about  disabled constructor

 Yes, the default constructor is  disabled BUT I am not using that one. I  
 want to use the other one - the custom constructor. I guess it signals  
 an error because it has those defaults parameters. But shouldn't the  
 compiler choose that one?

You *are* using the default one. The one without parameters *is* the default constructor, and you are calling the constructor without parameters. One could argue that the compiler should choose the other constructor, and even that having default constructors is reasonable. However, D has gone the route of not having default constructors for structs. Instead, structs are defined to be trivially constructible from T.init. The workaround is to use static opCall: struct Scene { Array!Surface objects; Array!Light lights; /*private*/ BVHNode root; disable this(); this(size_t objectReserveSpace = 20, size_t lightReserveSpace = 3) { objects.reserve(objectReserveSpace); lights.reserve(lightReserveSpace); } static Scene opCall(size_t objectReserveSpace = 20, size_t lightReserveSpace = 3) { return Scene(objectReserveSpace, lightReserveSpace); } } -- Simen
Jan 22 2013
parent =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
On 01/22/2013 11:07 AM, Simen Kjaeraas wrote:

 The workaround is to use static opCall:

 struct Scene
 {
 Array!Surface objects;
 Array!Light lights;

 /*private*/ BVHNode root;

  disable this();

That line must still be removed.
 this(size_t objectReserveSpace = 20, size_t lightReserveSpace = 3)

The default parameter values are not useful (or don't make sense) anymore: this(size_t objectReserveSpace, size_t lightReserveSpace)
 {
 objects.reserve(objectReserveSpace);
 lights.reserve(lightReserveSpace);
 }

 static Scene opCall(size_t objectReserveSpace = 20, size_t
 lightReserveSpace = 3)
 {
 return Scene(objectReserveSpace, lightReserveSpace);

Luckily that line calls the constructor, not itself and avoids an infinite loop. :) Ali
Jan 22 2013
prev sibling next sibling parent "Minas Mina" <minas_mina1990 hotmail.co.uk> writes:
 From Jonathan M Davis:

"...At this point, I don't think that the situation with default 
constructors and
structs is going to change. It's a result of requiring init 
properties for all
types, and is thus a "forced fault" in the language..."

Why does requiring init properties for all types results in this? 
What does that even mean?
Jan 22 2013
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Tuesday, 22 January 2013 at 21:12:50 UTC, Dmitry Olshansky 
wrote:
 23-Jan-2013 00:28, Minas Mina пишет:
 From Jonathan M Davis:

 "...At this point, I don't think that the situation with 
 default
 constructors and
 structs is going to change. It's a result of requiring init 
 properties
 for all
 types, and is thus a "forced fault" in the language..."

 Why does requiring init properties for all types results in 
 this? What
 does that even mean?

I don't buy it either. 0-argument constructor have nothing to do with requiring .init.

What he said.
Jan 22 2013
prev sibling next sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Wednesday, January 23, 2013 01:12:45 Dmitry Olshansky wrote:
 23-Jan-2013 00:28, Minas Mina пишет:
 From Jonathan M Davis:
 "...At this point, I don't think that the situation with default
 constructors and
 structs is going to change. It's a result of requiring init properties
 for all
 types, and is thus a "forced fault" in the language..."
 
 Why does requiring init properties for all types results in this? What
 does that even mean?

I don't buy it either. 0-argument constructor have nothing to do with requiring .init.

init prevents us from having _default_ constructors. As much as no-arg constructors are normally default constructors, they're technically separate concepts. static opCall shows us that it's perfectly possible to have no-arg constructors. However, there's a certain danger in simply making a no-arg constructor for structs not be a default constructor, as pretty much anyone coming from another OO language will expect it to be a default constructor. For the most part, static opCall solves the problem. - Jonathan M Davis
Jan 22 2013
prev sibling next sibling parent "deadalnix" <deadalnix gmail.com> writes:
On Tuesday, 22 January 2013 at 19:07:56 UTC, Simen Kjaeraas wrote:
 On 2013-01-22, 19:45, Minas Mina wrote:

 I have this structure:

 struct Scene
 {
 	Array!Surface objects;	// objects in the scene
 	Array!Light lights;		// lights in the scene
 	
 	/*private*/ BVHNode root;			// root node of the BVH tree
 	
 	 disable this(); // disable the default constructor because 
 space needs to be reserved for objects and lights
 	
 	this(size_t objectReserveSpace = 20, size_t lightReserveSpace 
 = 3)
 	{
 		objects.reserve(objectReserveSpace);
 		lights.reserve(lightReserveSpace);
 	}
 }



 auto scene = Scene(); // error about  disabled constructor

 Yes, the default constructor is  disabled BUT I am not using 
 that one. I want to use the other one - the custom 
 constructor. I guess it signals an error because it has those 
 defaults parameters. But shouldn't the compiler choose that 
 one?

You *are* using the default one. The one without parameters *is* the default constructor, and you are calling the constructor without parameters. One could argue that the compiler should choose the other constructor, and even that having default constructors is reasonable. However, D has gone the route of not having default constructors for structs. Instead, structs are defined to be trivially constructible from T.init.

Which incompatible with the desire of a NonNull construct.
 The workaround is to use static opCall:

 struct Scene
 {
 	Array!Surface objects;
 	Array!Light lights;
 	
 	/*private*/ BVHNode root;
 	
 	 disable this();
 	
 	this(size_t objectReserveSpace = 20, size_t lightReserveSpace 
 = 3)
 	{
 		objects.reserve(objectReserveSpace);
 		lights.reserve(lightReserveSpace);
 	}

 	static Scene opCall(size_t objectReserveSpace = 20, size_t 
 lightReserveSpace = 3)
 	{
 		return Scene(objectReserveSpace, lightReserveSpace);
 	}
 }

Cheap workaround as you cannot new.
Jan 22 2013
prev sibling next sibling parent "Simen Kjaeraas" <simen.kjaras gmail.com> writes:
On 2013-45-23 04:01, deadalnix <deadalnix gmail.com> wrote:

 On Tuesday, 22 January 2013 at 19:07:56 UTC, Simen Kjaeraas wrote:

 One could argue that the compiler should choose the other constructor,  
 and
 even that having default constructors is reasonable. However, D has gone
 the route of not having default constructors for structs. Instead,
 structs are defined to be trivially constructible from T.init.

Which incompatible with the desire of a NonNull construct.

Really? It's not like you cannot disable T.init. Please show how this is a problem for NonNull!T.
 Cheap workaround as you cannot new.

Indeed. But it's what we've got. -- Simen
Jan 22 2013
prev sibling next sibling parent "deadalnix" <deadalnix gmail.com> writes:
On Wednesday, 23 January 2013 at 08:00:05 UTC, Simen Kjaeraas 
wrote:
 On 2013-45-23 04:01, deadalnix <deadalnix gmail.com> wrote:

 On Tuesday, 22 January 2013 at 19:07:56 UTC, Simen Kjaeraas 
 wrote:

 One could argue that the compiler should choose the other 
 constructor, and
 even that having default constructors is reasonable. However, 
 D has gone
 the route of not having default constructors for structs. 
 Instead,
 structs are defined to be trivially constructible from T.init.

Which incompatible with the desire of a NonNull construct.

Really? It's not like you cannot disable T.init. Please show how this is a problem for NonNull!T.

Easy : NonNull!T bar; // bar is null NonNull!T[] barArray; barArray.length = 5; // barArray contains null elements. NonNull!T bar = something; foo(move(bar)); bar; // bar is null \o/ In fact they is so much way to create null stuff here that it isn't even remotely difficult to create a NonNull that is null;
Jan 23 2013
prev sibling next sibling parent "Simen Kjaeraas" <simen.kjaras gmail.com> writes:
On 2013-43-23 09:01, deadalnix <deadalnix gmail.com> wrote:

 On Wednesday, 23 January 2013 at 08:00:05 UTC, Simen Kjaeraas wrote:
 On 2013-45-23 04:01, deadalnix <deadalnix gmail.com> wrote:

 On Tuesday, 22 January 2013 at 19:07:56 UTC, Simen Kjaeraas wrote:

 One could argue that the compiler should choose the other  
 constructor, and
 even that having default constructors is reasonable. However, D has  
 gone
 the route of not having default constructors for structs. Instead,
 structs are defined to be trivially constructible from T.init.

Which incompatible with the desire of a NonNull construct.

Really? It's not like you cannot disable T.init. Please show how this is a problem for NonNull!T.

Easy : NonNull!T bar; // bar is null

Patently false. disable this() makes this fail with "initializer required for type".
 NonNull!T[] barArray;
 barArray.length = 5; // barArray contains null elements.

This is a known bug in the implementation of disable this().
 NonNull!T bar = something;
 foo(move(bar));
 bar; // bar is null \o/

Except it isn't. -- Simen
Jan 23 2013
prev sibling next sibling parent "deadalnix" <deadalnix gmail.com> writes:
On Wednesday, 23 January 2013 at 10:28:05 UTC, Simen Kjaeraas 
wrote:
 NonNull!T bar = something;
 foo(move(bar));
 bar; // bar is null \o/

Except it isn't.

move memcopy T.init . So it will be null.
Jan 23 2013
prev sibling next sibling parent "Simen Kjaeraas" <simen.kjaras gmail.com> writes:
On 2013-48-23 11:01, deadalnix <deadalnix gmail.com> wrote:

 On Wednesday, 23 January 2013 at 10:28:05 UTC, Simen Kjaeraas wrote:
 NonNull!T bar = something;
 foo(move(bar));
 bar; // bar is null \o/

Except it isn't.

move memcopy T.init . So it will be null.

Try it. Without a destructor, move does not revert to T.init. With a destructor you get this: src\phobos\std\algorithm.d(1564): Error: variable std.algorithm.move!(NotNull!(int*)).move.empty initializer required for type NotNull!(int*) Error: template instance std.algorithm.move!(NotNull!(int*)) error instantiating This is what disable this does, and what it's supposed to do. -- Simen
Jan 23 2013
prev sibling next sibling parent "Minas Mina" <minas_mina1990 hotmail.co.uk> writes:
Why is it required to have the .init property? Where is it useful 
and why was this decision made?
Jan 23 2013
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Wednesday, 23 January 2013 at 11:03:45 UTC, Minas Mina wrote:
 Why is it required to have the .init property? Where is it 
 useful and why was this decision made?

.init is very useful in the sense that it represents the raw object, *statically*. This is useful for declaring statics. It also makes constructors straightforward, in a simple 2 pass scheme (copy T.init, then run constructor). More importantly, the T.init state is also the one you are supposed to have post destruction (Or at least, calling a destructor on T.init is supposed to be safe). This allows for some *very* efficient and built-in move semantics: To move a into b, simply destroy b, memcopy a into b, memcopy T.init over a. Notice how massively simpler this is compared to C++'s explicit Rvalue references? I *dare* you to try doing that with C++. "DEFAULT" constructor breaks this entire scheme, as it implies that T.init is not an object's natural state. It jeopardizes a static destructible state. The problem is that this overlaps with having an "explicit constructor that takes no arguments". This is particularly problematic for objects that have pointers to payloads that represent "shallow" objects. AA's is a prime example of this: //---- void insert5(int[int] i) { i[5] = 5; } int[int] a; //Un-initialized T.init state int[int] b = [1 : 1]; //initialized int[int] c = [1 : 1]; c.remove(1); //Empty but initialized insert5(a); insert5(b); insert5(c); assert(a is null); //Oh no! assert(b == [1 : 1, 5 : 5]); //5 correctly appended assert(c == [5 : 5]); //5 correctly copied //---- In this example, I had to jump through loops to initialize c.
Jan 23 2013
prev sibling next sibling parent "deadalnix" <deadalnix gmail.com> writes:
On Wednesday, 23 January 2013 at 10:55:43 UTC, Simen Kjaeraas 
wrote:
 On 2013-48-23 11:01, deadalnix <deadalnix gmail.com> wrote:

 On Wednesday, 23 January 2013 at 10:28:05 UTC, Simen Kjaeraas 
 wrote:
 NonNull!T bar = something;
 foo(move(bar));
 bar; // bar is null \o/

Except it isn't.

move memcopy T.init . So it will be null.

Try it. Without a destructor, move does not revert to T.init. With a destructor you get this: src\phobos\std\algorithm.d(1564): Error: variable std.algorithm.move!(NotNull!(int*)).move.empty initializer required for type NotNull!(int*) Error: template instance std.algorithm.move!(NotNull!(int*)) error instantiating This is what disable this does, and what it's supposed to do.

Ho that is awesome ! Still many way of generating it on the heap, but it seems that the stack is getting better !
Jan 23 2013
prev sibling next sibling parent "Simen Kjaeraas" <simen.kjaras gmail.com> writes:
On 2013-14-23 13:01, deadalnix <deadalnix gmail.com> wrote:

 On Wednesday, 23 January 2013 at 10:55:43 UTC, Simen Kjaeraas wrote:
 On 2013-48-23 11:01, deadalnix <deadalnix gmail.com> wrote:

 On Wednesday, 23 January 2013 at 10:28:05 UTC, Simen Kjaeraas wrote:
 NonNull!T bar = something;
 foo(move(bar));
 bar; // bar is null \o/

Except it isn't.

move memcopy T.init . So it will be null.

Try it. Without a destructor, move does not revert to T.init. With a destructor you get this: src\phobos\std\algorithm.d(1564): Error: variable std.algorithm.move!(NotNull!(int*)).move.empty initializer required for type NotNull!(int*) Error: template instance std.algorithm.move!(NotNull!(int*)) error instantiating This is what disable this does, and what it's supposed to do.

Ho that is awesome ! Still many way of generating it on the heap, but it seems that the stack is getting better !

Hm. The heap. That should be new NotNull!(int*), and (new NotNull!(int*)[1])[0]. The former is correctly not allowed, and the latter is a different manifestation of the same bug mentioned previously. Of course, with casts and pointers, anything is possible. -- Simen
Jan 23 2013
prev sibling next sibling parent "deadalnix" <deadalnix gmail.com> writes:
On Wednesday, 23 January 2013 at 13:26:47 UTC, Simen Kjaeraas 
wrote:
 On 2013-14-23 13:01, deadalnix <deadalnix gmail.com> wrote:

 On Wednesday, 23 January 2013 at 10:55:43 UTC, Simen Kjaeraas 
 wrote:
 On 2013-48-23 11:01, deadalnix <deadalnix gmail.com> wrote:

 On Wednesday, 23 January 2013 at 10:28:05 UTC, Simen 
 Kjaeraas wrote:
 NonNull!T bar = something;
 foo(move(bar));
 bar; // bar is null \o/

Except it isn't.

move memcopy T.init . So it will be null.

Try it. Without a destructor, move does not revert to T.init. With a destructor you get this: src\phobos\std\algorithm.d(1564): Error: variable std.algorithm.move!(NotNull!(int*)).move.empty initializer required for type NotNull!(int*) Error: template instance std.algorithm.move!(NotNull!(int*)) error instantiating This is what disable this does, and what it's supposed to do.

Ho that is awesome ! Still many way of generating it on the heap, but it seems that the stack is getting better !

Hm. The heap. That should be new NotNull!(int*), and (new NotNull!(int*)[1])[0]. The former is correctly not allowed, and the latter is a different manifestation of the same bug mentioned previously. Of course, with casts and pointers, anything is possible.

I was more thinking about slice and stuff like that. struct on the heap in general are not very handy.
Jan 23 2013
prev sibling next sibling parent "Minas Mina" <minas_mina1990 hotmail.co.uk> writes:
On Wednesday, 23 January 2013 at 12:07:48 UTC, monarch_dodra 
wrote:
 On Wednesday, 23 January 2013 at 11:03:45 UTC, Minas Mina wrote:
 Why is it required to have the .init property? Where is it 
 useful and why was this decision made?

.init is very useful in the sense that it represents the raw object, *statically*. This is useful for declaring statics. It also makes constructors straightforward, in a simple 2 pass scheme (copy T.init, then run constructor). More importantly, the T.init state is also the one you are supposed to have post destruction (Or at least, calling a destructor on T.init is supposed to be safe). This allows for some *very* efficient and built-in move semantics: To move a into b, simply destroy b, memcopy a into b, memcopy T.init over a. Notice how massively simpler this is compared to C++'s explicit Rvalue references? I *dare* you to try doing that with C++. "DEFAULT" constructor breaks this entire scheme, as it implies that T.init is not an object's natural state. It jeopardizes a static destructible state. The problem is that this overlaps with having an "explicit constructor that takes no arguments". This is particularly problematic for objects that have pointers to payloads that represent "shallow" objects. AA's is a prime example of this: //---- void insert5(int[int] i) { i[5] = 5; } int[int] a; //Un-initialized T.init state int[int] b = [1 : 1]; //initialized int[int] c = [1 : 1]; c.remove(1); //Empty but initialized insert5(a); insert5(b); insert5(c); assert(a is null); //Oh no! assert(b == [1 : 1, 5 : 5]); //5 correctly appended assert(c == [5 : 5]); //5 correctly copied //---- In this example, I had to jump through loops to initialize c.

Really good explanation. Thank you. So disabled default constructor was just a "side effect" of doing those that you described.
Jan 23 2013
prev sibling parent "Simen Kjaeraas" <simen.kjaras gmail.com> writes:
On 2013-01-22, 22:28, Jonathan M Davis wrote:

 For the most part, static opCall solves the problem.

Add disable this(); to make sure, though. Otherwise you're likely to forget the parentheses somewhere, and have a bug. -- Simen
Jan 23 2013