www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - What is the case against a struct post-blit default constructor?

reply "Malte Skarupke" <malteskarupke web.de> writes:
So this has been brought up many times 
(http://www.digitalmars.com/d/archives/digitalmars/D/Struct_no-arg_con
tructor_173172.html 
http://www.digitalmars.com/d/archives/digitalmars/D/learn/Default_constructor_fo
_structs_20997.html 
http://www.digitalmars.com/d/archives/digitalmars/D/struct_and_default_constructor_150016.html)

However there was never a good answer.

I would like a struct default constructor. My expected behavior 
for it is that the struct gets initialized to .init, then my 
destructor gets called so that I can do additional things if I 
want.
This destructor always gets called when my struct is created 
without arguments.

So for example

struct S
{
     this()
     {
         assert(bar == 10);
         foo = [5].ptr;
     }
     int * foo;
     int bar = 10;
}
S s;
assert(*s.foo == 5);

Possible cases against it:
- "It would be slower than just initializing to .init." My 
proposed solution: Make the default constructor optional. If I 
have a struct that doesn't define a default constructor, it is 
just intialized to .init. So I can have the speed if I want to. 
Also always run it post-blit (blitted from .init). Meaning I only 
need to use it for things that can only be initialized at runtime.
- "There already is a solution in  disable this(); static 
opCall() {...}." This is hacky and working against the language. 
It also makes it so that you can not allocate your struct on the 
heap.
- "Structs should be simple." I haven't heard this argument, but 
I could imagine someone making it. I think structs should not be 
simple. They are much too useful for that. Also them having copy 
constructors and opAssign indicates that structs aren't expected 
to be simple.
- "There would be a way around it by doing S s = S.init;" I don't 
think that's a problem. If users want to shoot themselves in the 
foot, let em. The important part is that they have to go out of 
their way to do it. You could also handle that case in the copy 
constructor or assignment operator. (which is an improvement to 
the current behavior where you have to handle that case in every 
single member function, see for example std.typecons.RefCounted)


So I really can't think of a reason for why you wouldn't want 
this. Yet this discussion has happened several times already. 
There is clear demand for it and very good reasons, such as those 
mentioned in all the linked discussions.

So why is this being rejected?

Cheers,
Malte
Oct 08 2012
next sibling parent "F i L" <witte2008 gmail.com> writes:
+1 to all of that.

If the only issue is performance, I think the best solution is 
just to Document with a warning against using default 
constructors in performance critical structs.
Oct 08 2012
prev sibling next sibling parent Gor Gyolchanyan <gor.f.gyolchanyan gmail.com> writes:
--047d7b3a7ff4452e6404cb9e3e6e
Content-Type: text/plain; charset=UTF-8

I agree. AFAIK, D's policy is "give a safe default and a back door around
it".

On Tue, Oct 9, 2012 at 1:25 AM, F i L <witte2008 gmail.com> wrote:

 +1 to all of that.

 If the only issue is performance, I think the best solution is just to
 Document with a warning against using default constructors in performance
 critical structs.

-- Bye, Gor Gyolchanyan. --047d7b3a7ff4452e6404cb9e3e6e Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: quoted-printable I agree. AFAIK, D&#39;s policy is &quot;give a safe default and a back door= around it&quot;.<br><br><div class=3D"gmail_quote">On Tue, Oct 9, 2012 at = 1:25 AM, F i L <span dir=3D"ltr">&lt;<a href=3D"mailto:witte2008 gmail.com"= target=3D"_blank">witte2008 gmail.com</a>&gt;</span> wrote:<br> <blockquote class=3D"gmail_quote" style=3D"margin:0 0 0 .8ex;border-left:1p= x #ccc solid;padding-left:1ex">+1 to all of that.<br> <br> If the only issue is performance, I think the best solution is just to Docu= ment with a warning against using default constructors in performance criti= cal structs.<br> </blockquote></div><br><br clear=3D"all"><div><br></div>-- <br>Bye,<br>Gor = Gyolchanyan.<br> --047d7b3a7ff4452e6404cb9e3e6e--
Oct 09 2012
prev sibling next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Monday, October 08, 2012 18:47:43 Malte Skarupke wrote:
 So I really can't think of a reason for why you wouldn't want
 this. Yet this discussion has happened several times already.
 There is clear demand for it and very good reasons, such as those
 mentioned in all the linked discussions.
 
 So why is this being rejected?

It buys you pretty much nothing. There are plenty of places in the language where init is required (e.g. member variables that can't be directly initialized and the elements in an array). So, init _will_ be used regardless of what you do with the default constructor. If you want to prevent that, then you need to disable init, which we can already do. But you're not going to get those things initialized with the default constructor, which kind of defeats the purpose of the default constructor. If you can't guarantee that every instance which isn't explicitly constructed is default constructed, then what's the point? And if you want to have a way to explicitly construct a struct with no arguments, then you can use static opCall. Voila. Problem solved. What else do you need, given that you can't possibly have the struct call a default constructor everywhere that it's default initialized? There's the issue of S s; being S.init, but that's _normal_. If you were to make it the same as S() when a default constructor were declared, then structs wouldn't act like every other single type in the language (which are all default initialized to their init values). It would also make it _far_ more likely for people to write a default constructor thinking that it was _always_ used and then end up with serious problems when it wasn't - e.g. when it wasn't called for the elements or arrays or for member variables or for module-level variables or for any other instance of the struct which has to use init. By making S s; use S.init and disallowing default constructors, we make it clear to people _far_ faster that they can't rely on any kind of default construction of structs in D. Adding a default constructor which worked with S s; would just hide the problem and give people the false impression that they could rely on default construction. static opCall provides a means of constructing a struct with no arguments if that's what you want. So, as far as I can see, it pretty much just comes down to whether S s; is changed to be default constructed rather than default initialized when a default constructor is declared. And I think that that's truly counterproductive. It makes structs act inconsistently with other types and misleads people as to how default initialization works in D, which will only cause more bugs. The _only_ way that this could be reasonably solved is if you could somehow make it so that every time that a struct is default-initialized, it's default- constructed instead, and D just doesn't work that way. Too many things rely on init and the fact that it's known at compile time. So, IMHO adding default constructors to structs would be not only pointless but counterproductive. - Jonathan M Davis
Oct 10 2012
parent reply Don Clugston <dac nospam.com> writes:
On 10/10/12 11:21, Jonathan M Davis wrote:
 On Monday, October 08, 2012 18:47:43 Malte Skarupke wrote:
 So I really can't think of a reason for why you wouldn't want
 this. Yet this discussion has happened several times already.
 There is clear demand for it and very good reasons, such as those
 mentioned in all the linked discussions.

 So why is this being rejected?

It buys you pretty much nothing. There are plenty of places in the language where init is required (e.g. member variables that can't be directly initialized and the elements in an array). So, init _will_ be used regardless of what you do with the default constructor. If you want to prevent that, then you need to disable init, which we can already do. But you're not going to get those things initialized with the default constructor, which kind of defeats the purpose of the default constructor. If you can't guarantee that every instance which isn't explicitly constructed is default constructed, then what's the point?

Of course there would be no point. You have not answered the question. The issue is, WHY can we not guarantee that that the struct default constructor is called? I have a vague memory that Walter mentioned a technical difficulty once but I don't remember anything about what it was. I can't imagine what it would be. Even in the worst case, it would be possible to run CTFE on the default constructor in order to create .init. This would limit the default constructor to things which are CTFEable, but even that would still be useful for templated structs. Really, there does not seem to me to be any point in having an invariant for a struct, without a default constructor. BTW .init doesn't really work for nested structs anyway. There are several open bugs related to that.
Oct 10 2012
next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 10/10/2012 12:45 PM, Don Clugston wrote:
 On 10/10/12 11:21, Jonathan M Davis wrote:
 On Monday, October 08, 2012 18:47:43 Malte Skarupke wrote:
 So I really can't think of a reason for why you wouldn't want
 this. Yet this discussion has happened several times already.
 There is clear demand for it and very good reasons, such as those
 mentioned in all the linked discussions.

 So why is this being rejected?

It buys you pretty much nothing. There are plenty of places in the language where init is required (e.g. member variables that can't be directly initialized and the elements in an array). So, init _will_ be used regardless of what you do with the default constructor. If you want to prevent that, then you need to disable init, which we can already do. But you're not going to get those things initialized with the default constructor, which kind of defeats the purpose of the default constructor. If you can't guarantee that every instance which isn't explicitly constructed is default constructed, then what's the point?

Of course there would be no point. You have not answered the question. The issue is, WHY can we not guarantee that that the struct default constructor is called?

Because the current language does not. :o) Because that would imply disabling .init. - Which defeats the .init idiom in generic code. I am not convinced that we need that though. (T x)=>... does the job just fine, and .init has issues, as eg. int.init has special implicit conversion rules that not all members of the type int share. - Need a new (unsafe) language feature in order to be able to implement eg. 'emplace'.
 I have a vague memory that Walter mentioned a technical difficulty once
 but I don't remember anything about what it was.

 I can't imagine what it would be.

+1.
 Even in the worst case, it would be
 possible to run CTFE on the default constructor in order to create
 .init. This would limit the default constructor to things which are
 CTFEable, but even that would still be useful for templated structs.

You could run CTFE on the default constructor iff .init is requested, but I don't really see the point. What would be the benefit?
 Really, there does not seem to me to be any point in having an invariant
 for a struct, without a default constructor.

One can use a dented invariant. struct S{ bool valid = false; // ... invariant(){ if(valid) assert(...); } void establishInvariant()out{assert(valid);}body{...} }
 BTW .init doesn't really work for nested structs anyway. There are
 several open bugs related to that.

That is true, what are the plans for that? Not being able to have a field of a local struct type is a serious limitation. eg: struct Delay(T){ // where 'T' could easily be a struct from eg. // std.algorithm which is instantiated locally. T delegate() dg; T value; // ??? // invariant !dg ==> value.__context ref T compute(){ if(dg){ value = dg(); dg = null; } return value; } alias compute this; } auto delay(T)(T delegate() dg){ return Delay!T(dg); } (Built-in nullable types would solve the issue of course.)
Oct 10 2012
parent reply Don Clugston <dac nospam.com> writes:
On 10/10/12 13:27, Timon Gehr wrote:
 On 10/10/2012 12:45 PM, Don Clugston wrote:
 On 10/10/12 11:21, Jonathan M Davis wrote:
 On Monday, October 08, 2012 18:47:43 Malte Skarupke wrote:
 So I really can't think of a reason for why you wouldn't want
 this. Yet this discussion has happened several times already.
 There is clear demand for it and very good reasons, such as those
 mentioned in all the linked discussions.

 So why is this being rejected?

It buys you pretty much nothing. There are plenty of places in the language where init is required (e.g. member variables that can't be directly initialized and the elements in an array). So, init _will_ be used regardless of what you do with the default constructor. If you want to prevent that, then you need to disable init, which we can already do. But you're not going to get those things initialized with the default constructor, which kind of defeats the purpose of the default constructor. If you can't guarantee that every instance which isn't explicitly constructed is default constructed, then what's the point?

Of course there would be no point. You have not answered the question. The issue is, WHY can we not guarantee that that the struct default constructor is called?

Because the current language does not. :o) Because that would imply disabling .init. - Which defeats the .init idiom in generic code. I am not convinced that we need that though.

I don't like the .init idiom, I'm not sure it actually works. The semantics of .init aren't well defined. There is no guarantee that it is a valid value of the type. (T x)=>... does the job just fine, and
     .init has issues, as eg. int.init has special implicit conversion
     rules that not all members of the type int share.
   - Need a new (unsafe) language feature in order to be able to
     implement eg. 'emplace'.

Exactly.
 I have a vague memory that Walter mentioned a technical difficulty once
 but I don't remember anything about what it was.

 I can't imagine what it would be.

+1.
 Even in the worst case, it would be
 possible to run CTFE on the default constructor in order to create
 .init. This would limit the default constructor to things which are
 CTFEable, but even that would still be useful for templated structs.

You could run CTFE on the default constructor iff .init is requested, but I don't really see the point. What would be the benefit?

You could have more complicated relationships between members. struct W { int x; int y; } W bar(T)() { return W(T.sizeof, T.alignof); } struct Foo(T) { int m; int n; int k; this() { W w = bar!(T)(); m = w.x; n = w.y; k = abs(64 - m - n); } } of course it gains in value as bar!()() gets more complicated.
 Really, there does not seem to me to be any point in having an invariant
 for a struct, without a default constructor.

One can use a dented invariant. struct S{ bool valid = false; // ... invariant(){ if(valid) assert(...); } void establishInvariant()out{assert(valid);}body{...} }

Yes, you have to do something like that. It's absolute garbage. When you have a hack like that, I don't see the point of having invariants in the language.
 BTW .init doesn't really work for nested structs anyway. There are
 several open bugs related to that.

That is true, what are the plans for that?

Don't know.
Oct 10 2012
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 10/10/2012 01:59 PM, Don Clugston wrote:
 On 10/10/12 13:27, Timon Gehr wrote:
 On 10/10/2012 12:45 PM, Don Clugston wrote:
 ...
 Really, there does not seem to me to be any point in having an invariant
 for a struct, without a default constructor.

One can use a dented invariant. struct S{ bool valid = false; // ... invariant(){ if(valid) assert(...); } void establishInvariant()out{assert(valid);}body{...} }

Yes, you have to do something like that. It's absolute garbage. When you have a hack like that, I don't see the point of having invariants in the language. ...

Well, all invariants in Spec# follow this pattern. Every object has an implicit boolean 'valid' field.
Oct 11 2012
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 10/10/12 6:45 AM, Don Clugston wrote:
 On 10/10/12 11:21, Jonathan M Davis wrote:
 On Monday, October 08, 2012 18:47:43 Malte Skarupke wrote:
 So I really can't think of a reason for why you wouldn't want
 this. Yet this discussion has happened several times already.
 There is clear demand for it and very good reasons, such as those
 mentioned in all the linked discussions.

 So why is this being rejected?

It buys you pretty much nothing. There are plenty of places in the language where init is required (e.g. member variables that can't be directly initialized and the elements in an array). So, init _will_ be used regardless of what you do with the default constructor. If you want to prevent that, then you need to disable init, which we can already do. But you're not going to get those things initialized with the default constructor, which kind of defeats the purpose of the default constructor. If you can't guarantee that every instance which isn't explicitly constructed is default constructed, then what's the point?

Of course there would be no point. You have not answered the question. The issue is, WHY can we not guarantee that that the struct default constructor is called?

We could (after all, C++ does it). There are a few disadvantages to doing so, however. 1. Defining static data is more difficult. Currently, all static data is statically-initialized. With default constructors, we'd need to define the pre-construction state of such objects anyway, and then change the compiler to call constructors prior to main(). I find the current design simpler and easier to use. 2. Creating a temporary object cannot be anymore assumed to be a O(1), no-resources-allocated deal. Instead, generic code must conservatively assume that objects are always arbitrarily expensive to create. That makes some generic functions more difficult to implement. 3. Two-phase object destruction (releasing state and then deallocating memory), which is useful, is made more difficult by default constructors. Essentially the .init "pre-default-constructor" state intervenes in all such cases and makes it more difficult for language users to define and understand object states. 4. Same as above applies to an object post a move operation. What state is the object left after move? C++'s approach to this, forced by the existence of default constructors and other historical artifacts, has a conservative approach that I consider inferior to D's: the state of moved-from object is decided by the library, there's often unnecessary copying, and is essentially unspecified except that "it's valid" so the moved-from object can continue to be used. This is in effect a back-door introduction of a "no-resources-allocated" state for objects, which is what default constructors so hard tried to avoid in the first place. 5. There are a few minor issues such as correct array creation etc. but I don't consider them decisive. There are obvious disadvantages of the lack of a default constructor. I believe they are overcome by the advantages, although clearly reasonable people may disagree.
 I have a vague memory that Walter mentioned a technical difficulty once
 but I don't remember anything about what it was.

 I can't imagine what it would be. Even in the worst case, it would be
 possible to run CTFE on the default constructor in order to create
 ..init. This would limit the default constructor to things which are
 CTFEable, but even that would still be useful for templated structs.

Allowing a default constructor that's computable during compilation would be a very interesting idea.
 Really, there does not seem to me to be any point in having an invariant
 for a struct, without a default constructor.

Could you please give a few examples? (Honest question.) Most structures I define have an obvious quiescent state that vacuously satisfies the invariant. Exceptions that come to mind are: (a) value types that must always allocate something on the heap, see e.g. the contortions in std.container; (b) values as permits (the existence of the value guarantees a resource has been secured, as in scoped locks on mutexes). Andrei
Oct 11 2012
next sibling parent reply deadalnix <deadalnix gmail.com> writes:
Le 11/10/2012 14:19, Andrei Alexandrescu a écrit :
 We could (after all, C++ does it). There are a few disadvantages to
 doing so, however.

 1. Defining static data is more difficult. Currently, all static data is
 statically-initialized. With default constructors, we'd need to define
 the pre-construction state of such objects anyway, and then change the
 compiler to call constructors prior to main(). I find the current design
 simpler and easier to use.

CTFE is probably the answer here.
 2. Creating a temporary object cannot be anymore assumed to be a O(1),
 no-resources-allocated deal. Instead, generic code must conservatively
 assume that objects are always arbitrarily expensive to create. That
 makes some generic functions more difficult to implement.

Temporary object are used to store temporary state coming from some computation. In this case, the computation create the complexity, the object isn't created via default constructor. Or have you a use case in mind I don't think of ?
 3. Two-phase object destruction (releasing state and then deallocating
 memory), which is useful, is made more difficult by default
 constructors. Essentially the .init "pre-default-constructor" state
 intervenes in all such cases and makes it more difficult for language
 users to define and understand object states.

This one is made worse by the current state. You have to assume everywhere that your struct can be .init Even when it doesn't make any sense. RefCounted is a pathologic case of that.
 4. Same as above applies to an object post a move operation. What state
 is the object left after move? C++'s approach to this, forced by the
 existence of default constructors and other historical artifacts, has a
 conservative approach that I consider inferior to D's: the state of
 moved-from object is decided by the library, there's often unnecessary
 copying, and is essentially unspecified except that "it's valid" so the
 moved-from object can continue to be used. This is in effect a back-door
 introduction of a "no-resources-allocated" state for objects, which is
 what default constructors so hard tried to avoid in the first place.

If we give struct a giveaway state (where the struct cannot be used unless it is reinitilized to a correct value) this problem disappear. Except in the case 5. (and heap allocated struct in general), that in fact seems to me the major issue.
 5. There are a few minor issues such as correct array creation etc. but
 I don't consider them decisive.

Oct 11 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 10/11/12 9:52 AM, deadalnix wrote:
 Le 11/10/2012 14:19, Andrei Alexandrescu a écrit :
 Temporary object are used to store temporary state coming from some
 computation. In this case, the computation create the complexity, the
 object isn't created via default constructor.

 Or have you a use case in mind I don't think of ?

Here I meant a named temporary, such as e.g. a pivot in quicksort.
 3. Two-phase object destruction (releasing state and then deallocating
 memory), which is useful, is made more difficult by default
 constructors. Essentially the .init "pre-default-constructor" state
 intervenes in all such cases and makes it more difficult for language
 users to define and understand object states.

This one is made worse by the current state. You have to assume everywhere that your struct can be .init Even when it doesn't make any sense. RefCounted is a pathologic case of that.

One point I was making is that even with a default constructor, the definition and existence of .init pops all over the place, like a bubble in the carpet. I think it is a good engineering decision to simply make it the default state.
 4. Same as above applies to an object post a move operation. What state
 is the object left after move? C++'s approach to this, forced by the
 existence of default constructors and other historical artifacts, has a
 conservative approach that I consider inferior to D's: the state of
 moved-from object is decided by the library, there's often unnecessary
 copying, and is essentially unspecified except that "it's valid" so the
 moved-from object can continue to be used. This is in effect a back-door
 introduction of a "no-resources-allocated" state for objects, which is
 what default constructors so hard tried to avoid in the first place.

If we give struct a giveaway state (where the struct cannot be used unless it is reinitilized to a correct value) this problem disappear.

It doesn't disappear - it manifests itself in a different way (complexity in the language definition).
 Except in the case 5. (and heap allocated struct in general), that in
 fact seems to me the major issue.

I agree heaps with required allocated state are unpleasant to deal with in the current design. My overall point, which is worth repeating, is not that these problems are insurmountable. Instead, we're looking at different choices with distinct tradeoffs. There's no solution with all pluses. Andrei
Oct 11 2012
parent deadalnix <deadalnix gmail.com> writes:
Le 11/10/2012 16:35, Andrei Alexandrescu a écrit :
 On 10/11/12 9:52 AM, deadalnix wrote:
 Le 11/10/2012 14:19, Andrei Alexandrescu a écrit :
 Temporary object are used to store temporary state coming from some
 computation. In this case, the computation create the complexity, the
 object isn't created via default constructor.

 Or have you a use case in mind I don't think of ?

Here I meant a named temporary, such as e.g. a pivot in quicksort.

Well a pivot is not built out of nothing. So I don't see why any constructor have to be involved here. However, postblit will be involved and can be of arbitrary complexity already.
 3. Two-phase object destruction (releasing state and then deallocating
 memory), which is useful, is made more difficult by default
 constructors. Essentially the .init "pre-default-constructor" state
 intervenes in all such cases and makes it more difficult for language
 users to define and understand object states.

This one is made worse by the current state. You have to assume everywhere that your struct can be .init Even when it doesn't make any sense. RefCounted is a pathologic case of that.

One point I was making is that even with a default constructor, the definition and existence of .init pops all over the place, like a bubble in the carpet. I think it is a good engineering decision to simply make it the default state.

A giveaway state (as compile time state, not as runtime state) solve that problem nicely.
 4. Same as above applies to an object post a move operation. What state
 is the object left after move? C++'s approach to this, forced by the
 existence of default constructors and other historical artifacts, has a
 conservative approach that I consider inferior to D's: the state of
 moved-from object is decided by the library, there's often unnecessary
 copying, and is essentially unspecified except that "it's valid" so the
 moved-from object can continue to be used. This is in effect a back-door
 introduction of a "no-resources-allocated" state for objects, which is
 what default constructors so hard tried to avoid in the first place.

If we give struct a giveaway state (where the struct cannot be used unless it is reinitilized to a correct value) this problem disappear.

It doesn't disappear - it manifests itself in a different way (complexity in the language definition).

The complexity will have to be introduced anyway to handle disable this() properly, so I don't really see that as an added complexity. It is more like getting the most of the complexity that is already planned to be added.
 Except in the case 5. (and heap allocated struct in general), that in
 fact seems to me the major issue.

I agree heaps with required allocated state are unpleasant to deal with in the current design. My overall point, which is worth repeating, is not that these problems are insurmountable. Instead, we're looking at different choices with distinct tradeoffs. There's no solution with all pluses.

Well, why not let the programer make the choice ? Nothing have to change in regard to struct with no default constructor. And no complexity is added for existing stuffs.
Oct 11 2012
prev sibling next sibling parent reply deadalnix <deadalnix gmail.com> writes:
Le 11/10/2012 14:19, Andrei Alexandrescu a écrit :
 Could you please give a few examples? (Honest question.) Most structures
 I define have an obvious quiescent state that vacuously satisfies the
 invariant. Exceptions that come to mind are: (a) value types that must
 always allocate something on the heap, see e.g. the contortions in
 std.container; (b) values as permits (the existence of the value
 guarantees a resource has been secured, as in scoped locks on mutexes).

invariant will explode at you face at runtime any time you use the struct wrong where a default constructor would have prevented such use in the first place. Worse, the faulty case can be created at any place where the struct is used and is likely to create a problem. In fact, such design rely on the well known « a good programmer don't do ... » which is known to be a very good way to design hard to use and error prone constructs.
Oct 11 2012
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 10/11/12 9:56 AM, deadalnix wrote:
 Le 11/10/2012 14:19, Andrei Alexandrescu a écrit :
 Could you please give a few examples? (Honest question.) Most structures
 I define have an obvious quiescent state that vacuously satisfies the
 invariant. Exceptions that come to mind are: (a) value types that must
 always allocate something on the heap, see e.g. the contortions in
 std.container; (b) values as permits (the existence of the value
 guarantees a resource has been secured, as in scoped locks on mutexes).

invariant will explode at you face at runtime any time you use the struct wrong where a default constructor would have prevented such use in the first place.

I just mentioned that most of my structs have a natural invariant-abiding state.
 Worse, the faulty case can be created at any place where the struct is
 used and is likely to create a problem.

 In fact, such design rely on the well known « a good programmer don't do
 .... » which is known to be a very good way to design hard to use and
 error prone constructs.

There's a misunderstanding here. Andrei
Oct 11 2012
prev sibling next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 10/11/2012 02:19 PM, Andrei Alexandrescu wrote:
 [snip good points.]

Those should be in the faq.
 Really, there does not seem to me to be any point in having an invariant
 for a struct, without a default constructor.

Could you please give a few examples? (Honest question.) Most structures I define have an obvious quiescent state that vacuously satisfies the invariant. Exceptions that come to mind are: (a) value types that must always allocate something on the heap, see e.g. the contortions in std.container; (b) values as permits (the existence of the value guarantees a resource has been secured, as in scoped locks on mutexes). Andrei

I presume that many structures you define are templated and how obvious their default state is often depends on the particular instantiation. A large fraction of structs are local in idiomatic D code because of local template instantiation. Local structs do not have an obvious valid default state outside their local context and there is no way to tell if it is valid in generic code. Therefore, the issues you mentioned remain.
Oct 11 2012
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 10/11/2012 10:23 AM, Jonathan M Davis wrote:
 but there _are_ cases
 where you can't have an invariant because of it.

Except that you could write the invariant to be inclusive of the .init state.
Oct 11 2012
parent Dmitry Olshansky <dmitry.olsh gmail.com> writes:
On 12-Oct-12 01:16, monarch_dodra wrote:
 Except that you could write the invariant to be inclusive of the .init
 state.

Which would completely defeat the purpose of the invariant in many cases. The point is that it is invalid to use the init value. You can pass it around and set stuff to it and whatnot, but actually calling functions on it would be invalid, because its init state isn't valid. SysTime is a prime example of this, because it requires a valid TimeZone object, but its init value can't have one, because TimeZone is a class. So ideally, it would have an invariant which asserts that its TimeZone is non-null, but it can't have that, because opAssign unfortunately checks the invariant before it's called (which makes no sense to me - why would the state of the object prior to assignment matter? you're replacing it), so assigning a valid value to a default-initialized SysTime would fail the invariant. - Jonathan M Davis

This sounds more like a limitation of invariants, rather than a problem with .init. You make (imo) a valid point. Would it be complicated for opAssign to first check memcmp(this, T.init), and only do entry invariant check if the comparison fails? Potentially ditto on exit.

isn't. -- Dmitry Olshansky
Oct 11 2012
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Wednesday, 10 October 2012 at 09:48:27 UTC, Jonathan M Davis 
wrote:
 [SNIP]

 - Jonathan M Davis

After thinking about it, a lot, I think that would actually be the correct solution. I was an advocate of the "no-arg" constructor (not necessarily default), but I think it would end up being ambiguous in things like "emplace(&t)": "Do you want .init, or .__ctor()?" This, I think makes sense: T t; //T.init T t = T(); //opCall As for "new()", one can just two line it: p = new T(); //T.init p = T(); //opCall It takes two lines, but it makes a perfect distinction between .init and T(). -------- But are you telling is that the "opCall trick" is now official sanctioned as the way of initializing things that may need initialization, even though no arguments are passed? Can I roll it out in std.container? In RefCounted? In random? They are all "payload" structs, and they are in desperate need of some formal way to say: "I want you initialized with your payloads and ready for use, but I have nothing to give you."
Oct 10 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Wednesday, October 10, 2012 11:43:53 monarch_dodra wrote:
 But are you telling is that the "opCall trick" is now official
 sanctioned as the way of initializing things that may need
 initialization, even though no arguments are passed?

I believe that it has been that way for ages. There's really no other way to do it unless you provide another function that does exactly the same thing, which. e.g. auto s = constructS(); And I don't see how that buys you anything over using static opCall. - Jonathan M Davis
Oct 10 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Wednesday, October 10, 2012 12:45:20 Don Clugston wrote:
 Of course there would be no point.
 You have not answered the question. The issue is, WHY can we not
 guarantee that that the struct default constructor is called?
 
 I have a vague memory that Walter mentioned a technical difficulty once
 but I don't remember anything about what it was.
 
 I can't imagine what it would be.

I know that one of the issues was exceptions, though that could be solved by forcing them to be nothrow. There were others, but I don't remember the details. Certainly, from what I recall, the situation is such that you'd have to restrict a default construtor so thoroughly that it would be pretty much pointless to have one at all.
 Even in the worst case, it would be
 possible to run CTFE on the default constructor in order to create
 .init. This would limit the default constructor to things which are
 CTFEable, but even that would still be useful for templated structs.

Of what real value is a default construct that must be CTFEable? If you can construct everything at compile time, then you can directly initialize each of the member variables. It's being able to run a default constructor at runtime that's useful, and since init must be known at compile time, you get into the whole issue of needing init when you can't use a default constructor. For default constructors to be of any real value, it would have to be possible to replace all instances of init with a call to the default constructor _at runtime_. As long as you're restricted to CTFE, you might as well just directly initialize the member variables to set the values appropriately in the init property. - Jonathan M Davis
Oct 10 2012
prev sibling next sibling parent "foobar" <foo bar.com> writes:
On Wednesday, 10 October 2012 at 11:23:11 UTC, Jonathan M Davis 
wrote:
 On Wednesday, October 10, 2012 12:45:20 Don Clugston wrote:
 Of course there would be no point.
 You have not answered the question. The issue is, WHY can we 
 not
 guarantee that that the struct default constructor is called?
 
 I have a vague memory that Walter mentioned a technical 
 difficulty once
 but I don't remember anything about what it was.
 
 I can't imagine what it would be.

I know that one of the issues was exceptions, though that could be solved by forcing them to be nothrow. There were others, but I don't remember the details. Certainly, from what I recall, the situation is such that you'd have to restrict a default construtor so thoroughly that it would be pretty much pointless to have one at all.
 Even in the worst case, it would be
 possible to run CTFE on the default constructor in order to 
 create
 .init. This would limit the default constructor to things 
 which are
 CTFEable, but even that would still be useful for templated 
 structs.

Of what real value is a default construct that must be CTFEable? If you can construct everything at compile time, then you can directly initialize each of the member variables. It's being able to run a default constructor at runtime that's useful, and since init must be known at compile time, you get into the whole issue of needing init when you can't use a default constructor. For default constructors to be of any real value, it would have to be possible to replace all instances of init with a call to the default constructor _at runtime_. As long as you're restricted to CTFE, you might as well just directly initialize the member variables to set the values appropriately in the init property. - Jonathan M Davis

Can you please elaborate on where the .init property is being relied on? This is an aspect of D I don't really understand. What's the difference between a no-arg ctor and one with args in relation to this requirement? Is this used somewhere in template code? I vaguely remember the clear() function is supposed to be related to this. Personally, I don't agree with Walter's stance on default initialization. An explicit init by the user is IMO better design since it is _explicit_ and therefore documents the programmer's intention in the code itself. i.e.: int x = 0; // this is explicit and states my intention int x; // is it intentionally == 0? Did I forget to set a meaningful value?
Oct 10 2012
prev sibling next sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Wednesday, October 10, 2012 13:40:06 foobar wrote:
 Can you please elaborate on where the .init property is being
 relied on? This is an aspect of D I don't really understand.
 What's the difference between a no-arg ctor and one with args in
 relation to this requirement?

init is used anywhere and everywhere that an instance of a type needs to be default-initialized. _One_ of those places is a local variable. Not all places where an instance of an object needs to be created can be initialized by the programmer. The prime example of this would be arrays. If you declare auto i = new int[](5); or int[12] s; all of the elements in the array need to be initialized or they'll be garbage, and there's no way for the programmer to indicate what values they should be. The whole point of init is to avoid having variables ever be garbage without the programmer explicitly asking for it. And having a default constructor wouldn't help one whit with arrays, because the values of their elements must be known at compile time (otherwise you couldn't directly initialize member variables or static variables or anything else which requires a value at compile time with an array). With init, the compiler can take advantage of the fact that it knows the init value at compile time to efficiently initialize the array. But even constructing objects sanely relies on init. All user-defined objects are fully initialized to what their member variables are directly initialized to before their constructors are even called. In the case of a struct, that's the struct's init value. It's not for a class, because you can't have a class separate from its reference (so it's the reference which gets the init value), but the class still has a state equivalent to a struct's init value, and that's the state that it has before any of its constructors are called. If it weren't for that, you'd get the insanity that C++ or Java have with regards to the state of objects prior to construction. C++ is particularly bad in that each derived class is created in turn, meaning that when a constructor is called, the object _is_ that class rather than the derived class that you're ultimately constructing (which means that things can go horribly wrong if you're stupid enough to call a virtual function from a constructor in C++). I believe that Java handles that somewhat better, but it gets bizarre ordering issues with regards to initializing member variables that cause problems if you try and alter member variables from base classes inside of a derived constructor. With D, the object is guaranteed to be in a sane state prior to construction. And without init, even if every place that an object is instantiated could be directly initialized by the programmer (which it can't), then you would either end up with garbage every time that a variable isn't directly initialized, or you'd have to directly initialize them all. In order for D's construction model to work, this would include directly initializing _all_ member variables even if the constructor then set them to something else (which would actually cause problems with const and immutable). And that would get _very_ annoying, even if it would be preferable for the local variable to require explicit initialization. Another case where init is required is out parameters. All out parameters are set to their init value when the function is called in order to avoid bugs caused by reading the value of an out parameter before it's set within the function. That wouldn't work at all without init. One of the more annoying AA bugs makes it so that if the foo function in this code aa[5] = foo(); throws, then aa[5] gets set with a init value of the element type. While this clearly shouldn't happen, imagine how much worse it would be if we didn't have init, and that element got set to garbage? There are probably other cases that I can't think of right now where init gets used - probably in the runtime if nowhere else. Every place that could possibly result in a variable being garbage _doesn't_ result in garbage, because we have init. And regardless of what the language does, there are definitely places where the standard library takes advantage of init. It uses it a lot for type inferrence, but it also uses it directly in places such as std.algorithm.move. Without init, it would end up dealing with garbage values. It's also a lifesaver in generic code, because without it, generic code _can't_ initialize variables in many cases. Take something like T t; if(cond) { ... t = getValue(); ... } else { ... t = getOtherValue(); ... } How on earth could a generic function initialize t without T.init? void? That's just begging for bugs when one the paths doesn't actually set t like it's supposed to. It doesn't know anything about the type and therefore doesn't know what a reasonable default value would be, so it can't possibly initialize t properly. I can understand prefering that local variables have to be directly initialized by the programmer, but it just doesn't scale. Having init is _far_more flexible and far more powerful. Any and every situation that might need to initialize a variable can do it. Without init, that just isn't possible. - Jonathan M Davis
Oct 10 2012
prev sibling next sibling parent "foobar" <foo bar.com> writes:
Thank you for explaining.
See comments inline.

On Wednesday, 10 October 2012 at 18:12:13 UTC, Jonathan M Davis 
wrote:
 On Wednesday, October 10, 2012 13:40:06 foobar wrote:
 Can you please elaborate on where the .init property is being
 relied on? This is an aspect of D I don't really understand.
 What's the difference between a no-arg ctor and one with args 
 in
 relation to this requirement?

init is used anywhere and everywhere that an instance of a type needs to be default-initialized. _One_ of those places is a local variable. Not all places where an instance of an object needs to be created can be initialized by the programmer. The prime example of this would be arrays. If you declare auto i = new int[](5); or int[12] s; all of the elements in the array need to be initialized or they'll be garbage, and there's no way for the programmer to indicate what values they should be. The whole point of init is to avoid having variables ever be garbage without the programmer explicitly asking for it. And having a default constructor wouldn't help one whit with arrays, because the values of their elements must be known at compile time (otherwise you couldn't directly initialize member variables or static variables or anything else which requires a value at compile time with an array). With init, the compiler can take advantage of the fact that it knows the init value at compile time to efficiently initialize the array.

I understand the idea of default initialization. I was more interested in the machinery and implementation details :) So let's dive in into those details: Arrays - without changing existing syntax we can use these semantics: auto a = new int[](5); // compiler calls T() for each instance int[12] b; // ditto This would be same as in C++. We could also expand the syntax and allow: auto b = new int[](5, 9); // init all instances to 9 auto b = new int[](5, int (int index) { return index; }); initializes each member via a function call. This can be generalized for multi dimensions.
 But even constructing objects sanely relies on init. All 
 user-defined objects
 are fully initialized to what their member variables are 
 directly initialized
 to before their constructors are even called. In the case of a 
 struct, that's
 the struct's init value. It's not for a class, because you 
 can't have a class
 separate from its reference (so it's the reference which gets 
 the init value),
 but the class still has a state equivalent to a struct's init 
 value, and
 that's the state that it has before any of its constructors are 
 called.

So for classes .init is null which complicates non-nullable classes. It seems the "solution" (more like a hack IMO) of disable _breaks_ the .init guaranty in the language.
 If it weren't for that, you'd get the insanity that C++ or Java 
 have with
 regards to the state of objects prior to construction. C++ is 
 particularly bad
 in that each derived class is created in turn, meaning that 
 when a constructor
 is called, the object _is_ that class rather than the derived 
 class that
 you're ultimately constructing (which means that things can go 
 horribly wrong
 if you're stupid enough to call a virtual function from a 
 constructor in C++).
 I believe that Java handles that somewhat better, but it gets 
 bizarre ordering
 issues with regards to initializing member variables that cause 
 problems if
 you try and alter member variables from base classes inside of 
 a derived
 constructor. With D, the object is guaranteed to be in a sane 
 state prior to
 construction.

C++ is insanely bad here mainly due to [virtual?] MI which doesn't affect D and Java _allows_ virtual methods in constructors, which I think is also "fixed" in the latest c++ standard. I don't know about the ordering problems you mention but AFAIK the complication arises with MI, not default initialization. It's just a matter of properly defining the inheritance semantics.
 And without init, even if every place that an object is 
 instantiated could be
 directly initialized by the programmer (which it can't), then 
 you would either
 end up with garbage every time that a variable isn't directly 
 initialized, or
 you'd have to directly initialize them all. In order for D's 
 construction
 model to work, this would include directly initializing _all_ 
 member variables
 even if the constructor then set them to something else (which 
 would actually
 cause problems with const and immutable). And that would get 
 _very_ annoying,
 even if it would be preferable for the local variable to 
 require explicit
 initialization.

You talk about: class C { immutable T val; // what to do here? this() { ... } } This can be solved be either requiring a ctor call at # or if none specified call T(), or we can require the init to happen in the ctor a-la C++ semantics.
 Another case where init is required is out parameters. All out 
 parameters are
 set to their init value when the function is called in order to 
 avoid bugs
 caused by reading the value of an out parameter before it's set 
 within the
 function. That wouldn't work at all without init.

Personally, I'd just get remove this feature from the lanuage, tuples are a far better design for returning multiple values and even with this feature intact, we could always use the default no-arg constructor. E.g void foo(out T val); becomes: void foo(out T val = T());
 One of the more annoying AA bugs makes it so that if the foo 
 function in this
 code

 aa[5] = foo();

 throws, then aa[5] gets set with a init value of the element 
 type. While this
 clearly shouldn't happen, imagine how much worse it would be if 
 we didn't have
 init, and that element got set to garbage?

I don't get this example. If foo throws than the calling code will get control. How would you ever get to read that garbage in aa[5]? The surrounding try catch block should take care of this explicitly anyway. E.g. try { aa[5] = foo(); // foo throws // ## do something with aa[5], this won't happen } catch { // Please handle aa[5] here explicitly. // } // do something with aa[5], works due to the explicit fix in the catch.
 There are probably other cases that I can't think of right now 
 where init gets
 used - probably in the runtime if nowhere else. Every place 
 that could
 possibly result in a variable being garbage _doesn't_ result in 
 garbage,
 because we have init.

 And regardless of what the language does, there are definitely 
 places where the
 standard library takes advantage of init. It uses it a lot for 
 type
 inferrence, but it also uses it directly in places such as 
 std.algorithm.move.
 Without init, it would end up dealing with garbage values. It's 
 also a
 lifesaver in generic code, because without it, generic code 
 _can't_ initialize
 variables in many cases. Take something like

 T t;

 if(cond)
 {
  ...
  t = getValue();
  ...
 }
 else
 {
  ...
  t = getOtherValue();
  ...
 }

 How on earth could a generic function initialize t without 
 T.init? void?
 That's just begging for bugs when one the paths doesn't 
 actually set t like
 it's supposed to. It doesn't know anything about the type and 
 therefore
 doesn't know what a reasonable default value would be, so it 
 can't possibly
 initialize t properly.

Isn't disable breaks those algorithms in phobos anyway? how would that work for non-nullable classes? To answer the above question, I'd say there's nothing wrong with init to void. This is what happens anyway since the .init isn't used and the optimizer will optimize it away.
 I can understand prefering that local variables have to be 
 directly
 initialized by the programmer, but it just doesn't scale. 
 Having init is
 _far_more flexible and far more powerful. Any and every 
 situation that might
 need to initialize a variable can do it. Without init, that 
 just isn't
 possible.

 - Jonathan M Davis

Again, thanks for the explanation. I have to say that on a general level I have to agree with Don's post and I don't see how the .init idiom generally "works" or is useful. I can't see anything in the above examples that shows that .init is absolutely required and we can't live without it. The only thing that worries me here is the reliance of the runtime/phobos on .init.
Oct 10 2012
prev sibling next sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Wednesday, October 10, 2012 21:11:29 foobar wrote:
 Arrays - without changing existing syntax we can use these
 semantics:
 
 auto a = new int[](5); // compiler calls T() for each instance
 int[12] b; // ditto

And what on earth does that buy you over init? That's what init _does_. And since the value needs to be known at compile time (otherwise arrays won't work for directly initializing _anything_ that needs to be initialized at compile time), a constructor really doesn't buy you anything here at all. init provides a nice, consistent way of initializing or assigning a value to a variable as well as providing a consistent default state for a type when you need to be able to set a variable of that type to a consistent, safe state (e.g. with std.algorithm.move). And as built-in types don't _have_ constructors, there's no other way for generic code to generically initialize anything.
 I don't get this example. If foo throws than the calling code
 will get control. How would you ever get to read that garbage in
 aa[5]? The surrounding try catch block should take care of this
 explicitly anyway.
 
 E.g.
 try {
 aa[5] = foo(); // foo throws
 // ## do something with aa[5], this won't happen
 } catch {
 // Please handle aa[5] here explicitly.
 //
 }
 //    do something with aa[5], works due to the explicit fix in
 the catch.

It's a compiler (druntime?) bug. T.init is inserted at aa[5] regardless of whether foo() returns or not. It's just the assigned to the correct result of foo succeeds. So, if an exception is thrown, you end up with a value at aa[5] when you're not supposed to: http://d.puremagic.com/issues/show_bug.cgi?id=3825 The fact that we have init saves us from it being set to garbage. In general, having init saves us from garbage where someone screws up, making behavior deterministic and therefore more easily caught, debugged, and fixed. It also saves us from the compiler complaining about stuff not being initialized when it really was like Java does.
 Isn't  disable breaks those algorithms in phobos anyway?

Which is why disable is kind of a sucky idea. But as long as init exists for a type, functions such as std.algorithm.move can use it. Without it, they're pretty much screwed. So, using disable will restrict what you can do with a type, but it least sane types can take advantage of such functions. Without init, _none_ could, or if they did, it would be unsafe.
 To answer the above question, I'd say there's nothing wrong with
 init to void. This is what happens anyway since the .init isn't
 used and the optimizer will optimize it away.

Using void is dangerous and should be avoided unless absolutely necessary, otherwise you run a high risk of ending up with garbage values, giving you non-determinstic behavior, which tends to lead to very nasty, hard-to-find bugs.
 Again, thanks for the explanation. I have to say that on a
 general level I have to agree with Don's post and I don't see how
 the .init idiom generally "works" or is useful. I can't see
 anything in the above examples that shows that .init is
 absolutely required and we can't live without it. The only thing
 that worries me here is the reliance of the runtime/phobos on
 .init.

init avoids all kind of initialization problems and is an absolute godsend for generic code - so much so that letting it be used for compile time reflection and type inferrence even when disabled (though it still couldn't be used in actual code) is under discussion. It would actually be pretty bad not to have init (which is why disable sucks). I, for one, am _very_ glad that we have init. I'm guessing that you haven't written much generic code in D if you think that init should go away. You pretty much _need_ something like init for a lot of the stuff that you have to do in template constraints and whatnot. D's use of init is far superior to either C++ or Java's approach IMHO. But regardless of its various pros or cons, it's here to stay. - Jonathan M Davis
Oct 10 2012
prev sibling next sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Wednesday, October 10, 2012 22:40:50 Jonathan M Davis wrote:
 D's use of init is far superior to either C++ or Java's approach IMHO. But
 regardless of its various pros or cons, it's here to stay.

Something to remember here is that there are multiple ways to solve some of these problems. Each has their pros and cons. D has gone with init and IMHO greatly from it. You can undoubtedly find alternate ways to deal with pretty much every use case for init. After all, other languages went with other solutions (each with their own pros and cons). But init is what D chose to go with, so regardless of whether it's the best choice or not, it's what we have, and it's far too late to change it now. So, while you may prefer another solution, because you prefer a different set of pros and cons, that doesn't mean that D's choice was a bad one. It just means that what it prioritizes isn't necessarily the same as what you prioritize. - Jonathan M Davis
Oct 10 2012
prev sibling next sibling parent "Malte Skarupke" <malteskarupke web.de> writes:
Hi, thanks for the detailed answers.

So I am very much for keeping init. I just want that if the user 
specifies a default constructor, it is being called after the 
value has been initialized to init.

 On Wednesday, October 10, 2012 21:11:29 foobar wrote:
 Arrays - without changing existing syntax we can use these
 semantics:
 
 auto a = new int[](5); // compiler calls T() for each instance
 int[12] b; // ditto

And what on earth does that buy you over init? That's what init _does_. And since the value needs to be known at compile time (otherwise arrays won't work for directly initializing _anything_ that needs to be initialized at compile time), a constructor really doesn't buy you anything here at all. init provides a nice, consistent way of initializing or assigning a value to a variable as well as providing a consistent default state for a type when you need to be able to set a variable of that type to a consistent, safe state (e.g. with std.algorithm.move). And as built-in types don't _have_ constructors, there's no other way for generic code to generically initialize anything.

"What does this buy you over init?" This would help with structs that can't be initialized to a valid value at compile time. So what I expect to happen in a line like S[12] s; Is that they all get blitted to init, followed by a loop that calls the default constructor on each of these. If there is no default constructor, they stay at init and the loop doesn't happen. This would work with user defined types and with built-in types. If a constructor throws an exception, then the array is correctly initialized up to the element before the throwing one. The remaining elements are at init. For member variables I expect this to happen: If one of your member variables has a default constructor but you do not, then the compiler will generate an empty default constructor for you. This gives your object the same behavior in arrays as described above, which means that the member variable will always be correctly initialized using both init and the deafult constructor. If one of your member variables has a default constructor, and so do you, then the member variable's default constructor is called before your default constructor. If an exception is thrown, the containing object remains at the state it is in (most likely init) If you do not want the default constructor of your member variable to be called, you can declare it as S s = S.init; For emplace I expect that the default constructor gets called. For std.algorithm.move you'd have to define which state the source object is in after moving. Since it's a destructive move, I'd expect the object to be at init, as if a destructor had been called. You could implement that with a tiny change to the current implementation. (memcpy from init instead of a statically allocated instance) This should cover all the cases that you mentioned. It is highly performant (in fact this is very close to the behavior in C++, but faster because it uses init) and it won't affect structs that can be initialized at compile time. It will only be used for cases where you need to do things at runtime, and the users have the full ability to shoot themselves in the foot if they want to.
Oct 10 2012
prev sibling next sibling parent Walter Bright <newshound2 digitalmars.com> writes:
On 10/8/2012 9:47 AM, Malte Skarupke wrote:
 So why is this being rejected?

So S.init is a valid instance of S.
Oct 11 2012
prev sibling next sibling parent "Simen Kjaeraas" <simen.kjaras gmail.com> writes:
On 2012-10-11, 15:52, deadalnix wrote:

 Le 11/10/2012 14:19, Andrei Alexandrescu a =C3=A9crit :
 We could (after all, C++ does it). There are a few disadvantages to
 doing so, however.

 1. Defining static data is more difficult. Currently, all static data=


 statically-initialized. With default constructors, we'd need to defin=


 the pre-construction state of such objects anyway, and then change th=


 compiler to call constructors prior to main(). I find the current des=


 simpler and easier to use.

CTFE is probably the answer here.

But not all functions are CTFE-able, so it's not a solution in all cases= . -- = Simen
Oct 11 2012
prev sibling next sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Thursday, October 11, 2012 08:19:23 Andrei Alexandrescu wrote:
 Could you please give a few examples? (Honest question.) Most structures
 I define have an obvious quiescent state that vacuously satisfies the
 invariant. Exceptions that come to mind are: (a) value types that must
 always allocate something on the heap, see e.g. the contortions in
 std.container; (b) values as permits (the existence of the value
 guarantees a resource has been secured, as in scoped locks on mutexes).

std.datetime.SysTime requires a valid TimeZone to function properly, but SysTime.init ends up with a null TimeZone, because it's a class, and you can't directly initialize a member variable with class object. The result of this is that SysTime can't have an invariant, because then SysTime.init would be invalid, and thanks to the fact that http://d.puremagic.com/issues/show_bug.cgi?id=5058 was resolved as invalid (the invariant gets called before opAssign even though I'd strongly argue that it shouldn't be), even assigning a valid value to a SysTime which was SysTime.init would blow up with an invariant. So, no invariant, even though it really should have one. Any situation where the init value is essentially invalid (like it would be with floating point types) makes it so that you can't have an invariant, and in many of those cases, having a default constructor which was always called would solve the problem. I'm still in favor of _not_ trying to add default constructors given all of the issues involved, and I agree that on the whole, init is a superior solution (even if it isn't perfect), but there _are_ cases where you can't have an invariant because of it. - Jonathan M Davis
Oct 11 2012
prev sibling next sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Thursday, October 11, 2012 13:06:34 Walter Bright wrote:
 On 10/11/2012 10:23 AM, Jonathan M Davis wrote:
 but there _are_ cases
 where you can't have an invariant because of it.

Except that you could write the invariant to be inclusive of the .init state.

Which would completely defeat the purpose of the invariant in many cases. The point is that it is invalid to use the init value. You can pass it around and set stuff to it and whatnot, but actually calling functions on it would be invalid, because its init state isn't valid. SysTime is a prime example of this, because it requires a valid TimeZone object, but its init value can't have one, because TimeZone is a class. So ideally, it would have an invariant which asserts that its TimeZone is non-null, but it can't have that, because opAssign unfortunately checks the invariant before it's called (which makes no sense to me - why would the state of the object prior to assignment matter? you're replacing it), so assigning a valid value to a default-initialized SysTime would fail the invariant. - Jonathan M Davis
Oct 11 2012
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Thursday, 11 October 2012 at 20:59:32 UTC, Jonathan M Davis 
wrote:
 On Thursday, October 11, 2012 13:06:34 Walter Bright wrote:
 On 10/11/2012 10:23 AM, Jonathan M Davis wrote:
 but there _are_ cases
 where you can't have an invariant because of it.

Except that you could write the invariant to be inclusive of the .init state.

Which would completely defeat the purpose of the invariant in many cases. The point is that it is invalid to use the init value. You can pass it around and set stuff to it and whatnot, but actually calling functions on it would be invalid, because its init state isn't valid. SysTime is a prime example of this, because it requires a valid TimeZone object, but its init value can't have one, because TimeZone is a class. So ideally, it would have an invariant which asserts that its TimeZone is non-null, but it can't have that, because opAssign unfortunately checks the invariant before it's called (which makes no sense to me - why would the state of the object prior to assignment matter? you're replacing it), so assigning a valid value to a default-initialized SysTime would fail the invariant. - Jonathan M Davis

This sounds more like a limitation of invariants, rather than a problem with .init. You make (imo) a valid point. Would it be complicated for opAssign to first check memcmp(this, T.init), and only do entry invariant check if the comparison fails? Potentially ditto on exit.
Oct 11 2012
prev sibling next sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Friday, October 12, 2012 01:20:44 Dmitry Olshansky wrote:
 
 This sounds more like a limitation of invariants, rather than a problem
 with .init. You make (imo) a valid point.
 
 Would it be complicated for opAssign to first check memcmp(this,
 T.init), and only do entry invariant check if the comparison fails?
 
 Potentially ditto on exit.

With your rule T.init is a valid state. AFAICT in Jonathan's example it isn't.

Yeah. All that's required is that you outright skip the call to the invariant before calling opAssign. It _does_ mean special casing opAssign, but I don't see that as a problem. I don't understand why it matters whether the object is valid before it's assigned to. Presumably, you're completely replacing its state, and regardless of what you actually do in the function, it would still need to be valid afterwards. So, it seems perfectly fine to me to just skip calling the variant before calling opAssign, but Walter was against it. His comment on the bug ( http://d.puremagic.com/issues/show_bug.cgi?id=5058 ) indicated that he thought that init should always be in a valid state, but since NaN and null are invalid states, I see no reason that a struct's init value can't be an invalid state. It can be copied and passed around just fine. It just wouldn't pass its invariant if you tried to call a function on it before assigning it a valid value. - Jonathan M Davis
Oct 11 2012
prev sibling next sibling parent "Malte Skarupke" <malteskarupke web.de> writes:
First of all thank you for the detailed responses.

I wrote a response yesterday but somehow the website seems to 
have swallowed it.

On Thursday, 11 October 2012 at 12:43:31 UTC, Andrei Alexandrescu 
wrote:
 We could (after all, C++ does it). There are a few 
 disadvantages to doing so, however.

 1. Defining static data is more difficult. Currently, all 
 static data is statically-initialized. With default 
 constructors, we'd need to define the pre-construction state of 
 such objects anyway, and then change the compiler to call 
 constructors prior to main(). I find the current design simpler 
 and easier to use.

This is a good reason. I like the idea of "no code gets run before main" Running code before main only lead to problems in C++. However those problems were always merely inconvenient, never big issues. I think it's not good to allow people to run code before main, but I think it's a bigger problem to have no default constructors.
 2. Creating a temporary object cannot be anymore assumed to be 
 a O(1), no-resources-allocated deal. Instead, generic code must 
 conservatively assume that objects are always arbitrarily 
 expensive to create. That makes some generic functions more 
 difficult to implement.

I think that is OK. The generic algorithm should assume that your object is cheap to create. In C++ all algorithms assume this and there are few issues. Sure, every now and then you pass an expensive-to-create object to an algorithm which creates instances, but that bug is very easy to debug.
 3. Two-phase object destruction (releasing state and then 
 deallocating memory), which is useful, is made more difficult 
 by default constructors. Essentially the .init 
 "pre-default-constructor" state intervenes in all such cases 
 and makes it more difficult for language users to define and 
 understand object states.

I'm not sure that I understand this. My two ways of interpreting this are: A) You mean that the compiler currently assumes that it doesn't have to call the destructor for objects that are at init. But after introducing a default constructor it would always have to call the destructor. I think that's OK. That's an optimization that's unlikely to give you much gain for types that need a destructor. B) You mean that if we introduce a default constructor, there would still be situations where an object is at init and it's destructor gets called. For example if people throw exceptions. And users might be confused by this when their destructor gets run and their object is at init, instead of the state that they expect. I think this is OK. It is the same situation that we currently have with static opCall(). Yes, with the static opCall() hack people kinda expect that their object isn't always initialized, so their destructors probably react better to the state being at init, but I think if the documentation states clearly "there are situations where the destructor will be called on an object whose default constructor was not called" then people can handle that situation just fine.
 4. Same as above applies to an object post a move operation. 
 What state is the object left after move? C++'s approach to 
 this, forced by the existence of default constructors and other 
 historical artifacts, has a conservative approach that I 
 consider inferior to D's: the state of moved-from object is 
 decided by the library, there's often unnecessary copying, and 
 is essentially unspecified except that "it's valid" so the 
 moved-from object can continue to be used. This is in effect a 
 back-door introduction of a "no-resources-allocated" state for 
 objects, which is what default constructors so hard tried to 
 avoid in the first place.

For moving you'd just have to define a state that the source object is in after moving. Since it's a destructive move I would expect the object to be at init after moving, as if the destructor had been called. If that is well defined, then I think users will be fine with it. This is actually the situation that we currently have, and users seem to be fine with it. This can be achieved with a tiny change to the current implementation of std.algorithm.move: Make it memcpy from init instead of a statically allocated value. I'd also like it if we could write all structs so that init is a valid state of the struct, as Walter suggests. However this is going to make certain things impossible in the language. Simple things like having shared data between multiple instances of a struct. Or counting how often objects of a certain type was allocated. Or iterating over all instances of a type. In fact there are parts of the standard library that don't work because they'd need a default constructor. One of the linked posts mentions this example from std.typecons: import std.typecons; { RefCounted!(int, RefCountedAutoInitialize.yes) a; assert(a == 0); // works RefCounted!(int, RefCountedAutoInitialize.no) b = a; assert(b == a); // works a = 5; assert(b == a); // works } { RefCounted!(int, RefCountedAutoInitialize.yes) a; //assert(a == 0); RefCounted!(int, RefCountedAutoInitialize.no) b = a; assert(b == a); // works a = 5; assert(b == a); // doesn't work } In this case it just means that that struct needs to be rewritten, because the whole "RefCountedAutoInitialize" thing is impossible in D. std.typecons.RefCounted should never be auto initialized. People have to always initialize it manually. Of course that also means that any type which uses this has to be initialized manually. And any type which uses that. So you can't really have arrays of RefCounted things. Or arrays of structs which use RefCounter. Basically you will come across situations where RefCounted doesn't do what you expect and then you'll have to hack around it. All because structs can't really have shared data between multiple instances. But really the bigger problem is not that this makes a small amount of features impossible. The bigger problem is that this means that you have to use classes for a large amount of things for which structs are better suited. Just because I have things that need to happen at creation time doesn't mean that I want it to be a class. Also, as has been brought up several times: It means you have to fight against the language if you want to use invariants. You have to always say "if it is in an invalid state, then everything is OK. Otherwise check if the state makes sense." You are all making good points, but I think the current state is not going to work long term in the real world. Heck, it already introduces problems in the standard library and hacks like static opCall are accepted wisdom. Sure, introducing a default constructor would create problems, but those are either minor or solvable.
Oct 11 2012
prev sibling next sibling parent "Simen Kjaeraas" <simen.kjaras gmail.com> writes:
On 2012-05-12 00:10, Jonathan M Davis <jmdavisProg gmx.com> wrote:

 On Friday, October 12, 2012 01:20:44 Dmitry Olshansky wrote:
 This sounds more like a limitation of invariants, rather than a  

 with .init. You make (imo) a valid point.

 Would it be complicated for opAssign to first check memcmp(this,
 T.init), and only do entry invariant check if the comparison fails?

 Potentially ditto on exit.

With your rule T.init is a valid state. AFAICT in Jonathan's example it isn't.

Yeah. All that's required is that you outright skip the call to the invariant before calling opAssign. It _does_ mean special casing opAssign, but I don't see that as a problem. I don't understand why it matters whether the object is valid before it's assigned to. Presumably, you're completely replacing its state, and regardless of what you actually do in the function, it would still need to be valid afterwards. So, it seems perfectly fine to me to just skip calling the variant before calling opAssign, but Walter was against it. His comment on the bug ( http://d.puremagic.com/issues/show_bug.cgi?id=5058 ) indicated that he thought that init should always be in a valid state, but since NaN and null are invalid states, I see no reason that a struct's init value can't be an invalid state. It can be copied and passed around just fine. It just wouldn't pass its invariant if you tried to call a function on it before assigning it a valid value.

The opAssign can presumably be more complex, and e.g. require deallocation of non-GC memory, releasing handles and whatnot. Anyways, is there a reason you cannot use disable this() for SysTime? That way, you have rather explicitly marked .init as invalid. -- Simen
Oct 11 2012
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Thursday, 11 October 2012 at 22:05:34 UTC, Jonathan M Davis
wrote:
 On Friday, October 12, 2012 01:20:44 Dmitry Olshansky wrote:
 
 This sounds more like a limitation of invariants, rather 
 than a problem
 with .init. You make (imo) a valid point.
 
 Would it be complicated for opAssign to first check 
 memcmp(this,
 T.init), and only do entry invariant check if the comparison 
 fails?
 
 Potentially ditto on exit.

With your rule T.init is a valid state. AFAICT in Jonathan's example it isn't.

Yeah. All that's required is that you outright skip the call to the invariant before calling opAssign. It _does_ mean special casing opAssign, but I don't see that as a problem. I don't understand why it matters whether the object is valid before it's assigned to. Presumably, you're completely replacing its state, and regardless of what you actually do in the function, it would still need to be valid afterwards. So, it seems perfectly fine to me to just skip calling the variant before calling opAssign, but Walter was against it. His comment on the bug ( http://d.puremagic.com/issues/show_bug.cgi?id=5058 ) indicated that he thought that init should always be in a valid state, but since NaN and null are invalid states, I see no reason that a struct's init value can't be an invalid state. It can be copied and passed around just fine. It just wouldn't pass its invariant if you tried to call a function on it before assigning it a valid value. - Jonathan M Davis

Yes, as answered, opAssign may do things to this, such as dealocate a payload, reduce a ref counter, or who knows what. As a matter of fact, there was a bug in emplace about that I recently fixed. My rational for skipping the test *ONLY* if "this == .init" is that .init is supposed to mean not yet fully initialized. This would make code such as: T t = t.init; //Not yet ready for use. //Attempt to use will assert the invariant ... t = T(5); //Initialize it later. Don't check invariant on entry ... //Use t and check invariants every time. ... t = t.init; //De-initialization, Don't check invariant on exit. //Future attempt for use will assert the invariant I'd also do the same for destructor entry: The language already states that .init SHOULD be a valid destructible state.
Oct 11 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Friday, October 12, 2012 06:42:23 Simen Kjaeraas wrote:
 Anyways, is there a reason you cannot use  disable this() for SysTime?
 That way,
 you have rather explicitly marked .init as invalid.

Disabling init does a lot to make a type unusable such that it really doesn't make sense to use it unless you need to. It's not that you can't have init. It's that you need to make sure that you actually set it to a valid value before calling any functions on it. If init were disabled, then you couldn't do stuff like SysTime[12] times; which should be perfectly fine as long as you make sure to set them all before actually calling any functions on them, e.g. foreach(i, ref t; times) t = calcTime(i); Think of it like NaN or null. You can have variable set to them, and it's not a problem. It's only once you try to use functions or operators on them that they blow up. - Jonathan M Davis
Oct 12 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Friday, October 12, 2012 07:53:09 monarch_dodra wrote:
 Yes, as answered, opAssign may do things to this, such as
 dealocate a payload, reduce a ref counter, or who knows what.

A valid point, but it would be easy to explicitly call the invariant at the beginning of opAssign if wanted to ensure that the object's state was valid at the beginning of opAssign. You can't _not_ call the invariant though if the compiler already does. And there's another problem which your suggest against init suggestion wouldn't fix. It's initializing to void: S s = void; If you ever want to do that, you can't have an invariant, or it'll blow up when you try and assign to it. And since it certainly wouldn't be the same as the init property, checking against the init property wouldn't help you any. - Jonathan M Davis
Oct 12 2012
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Friday, 12 October 2012 at 07:36:37 UTC, Jonathan M Davis 
wrote:
 On Friday, October 12, 2012 07:53:09 monarch_dodra wrote:
 Yes, as answered, opAssign may do things to this, such as
 dealocate a payload, reduce a ref counter, or who knows what.

A valid point, but it would be easy to explicitly call the invariant at the beginning of opAssign if wanted to ensure that the object's state was valid at the beginning of opAssign. You can't _not_ call the invariant though if the compiler already does. And there's another problem which your suggest against init suggestion wouldn't fix. It's initializing to void: S s = void; If you ever want to do that, you can't have an invariant, or it'll blow up when you try and assign to it. And since it certainly wouldn't be the same as the init property, checking against the init property wouldn't help you any. - Jonathan M Davis

Well, again, you'd need your s to be in a valid state to assign to it, so I'd *hope* the invariant blew up in my face. When you declare s void, you are supposed to memcpy .init over it manually, or call emplace (which does the same thing + more). And you CAN have an invariant: //---- import std.conv; import std.c.string; struct S { invariant() { assert(0); } } void main() { S s = void; static auto foo = S.init; memcpy(&s, &foo, S.sizeof); //or emplace(&s); } //---- TA-DA!!!
Oct 12 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Friday, October 12, 2012 10:09:22 monarch_dodra wrote:
 When you declare s void, you are supposed to memcpy .init over it
 manually, or call emplace (which does the same thing + more).

If that's what you're "supposed" to do, it's only because opAssign is annoying enough to check its invariant. Without the invariant, that's not something that would normally make sense to do. And it's _not_ what you do with a built- in type. int i = void; i = 5; is perfectly legal. I see no reason why S s = void; s = S(17); shouldn't be legal as well. And it _is_ legal, except when you're unlucky enough to need to define an opAssign for S, because then that'll blow up in your face if you have an invariant. And because the _only_ reason that using memcpy or emplace would be necessary (or really make any sense at all) is to work around the fact that the invariant is called before opAssign, that means that you have to contort your code specifically to make it work in non-release mode, making it _less_ efficient in release mode. And since usually the point of initializing to void is to gain extra efficiency, this is entirely counterproductive. I'd argue that _any_ code which is specifically doing emplace or memcpy because it initialized to void is probably a bad idea. If that's what it needs to do, it shouldn't have been initialized to void in the first place. And if it's because of the invariant, then it makes way more sense to ditch the invariant then use emplace like that. Really, I think that it's a bad design decision to require that the invariant be called before opAssign. It does _not_ play nice with some of D's other features, and the result is likely to be that invariants get used less, meaning that code is more likely to be buggy. - Jonathan M Davis
Oct 12 2012
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Friday, 12 October 2012 at 08:20:42 UTC, Jonathan M Davis
wrote:
 On Friday, October 12, 2012 10:09:22 monarch_dodra wrote:

 If that's what you're "supposed" to do, it's only because 
 opAssign is annoying
 enough to check its invariant. Without the invariant, that's 
 not something
 that would normally make sense to do. And it's _not_ what you 
 do with a built-
 in type.

 int i = void;
 i = 5;

 is perfectly legal. I see no reason why

 S s = void;
 s = S(17);

 [SNIP]

 - Jonathan M Davis

The issue with initializing with void actually has nothing to do with invariants. Try that code defining S as RefCounted!int and see what happens.
Oct 12 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Friday, October 12, 2012 10:29:06 monarch_dodra wrote:
 On Friday, 12 October 2012 at 08:20:42 UTC, Jonathan M Davis
 
 wrote:
 On Friday, October 12, 2012 10:09:22 monarch_dodra wrote:
 
 If that's what you're "supposed" to do, it's only because
 opAssign is annoying
 enough to check its invariant. Without the invariant, that's
 not something
 that would normally make sense to do. And it's _not_ what you
 do with a built-
 in type.
 
 int i = void;
 i = 5;
 
 is perfectly legal. I see no reason why
 
 S s = void;
 s = S(17);
 
 [SNIP]
 
 - Jonathan M Davis

The issue with initializing with void actually has nothing to do with invariants. Try that code defining S as RefCounted!int and see what happens.

That just means that the problem goes further than just invariants. It's still a big problem for invariants. - Jonathan M Davis
Oct 12 2012
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Friday, 12 October 2012 at 08:34:31 UTC, Jonathan M Davis 
wrote:
 
 Try that code defining S as RefCounted!int and see what 
 happens.

That just means that the problem goes further than just invariants. It's still a big problem for invariants. - Jonathan M Davis

I appologize, but I don't see how this is a "big problem". It is no different then doing: S s = void; s.__ctor(args); Initializing something to void _is_ unsafe, and must be used with precautions. opAssign is no different. If you want it called, then you _HAVE_ to make sure the target is valid first. The only reason: int a = void; a = 5; works is because a doesn't define an opAssign, and int doesn't have any invalid states anyways. It's once S becomes complex that you can't just go rushing in assigning and constructing without proper initialization. Using emplace makes the "problem" go away entirely*, as it will just do a straight-up memcopy if that is "good enough" (no extra cost for ints), and do "what is needed" for the rest (".init + .__ctor" or ".init + opAssign"). alias RefCounted!int S; int i = void; S s1 = void; S s2 = void; emplace(&i, 5); //OK! Do a memcpy assignement emplace(&s1, 5); //OK! Do a .init memcpy + .__ctor emplace(&s2, S(5)); //OK! Do a .init memcpy + .opAssign** *Technically, once my fix goes through. It currently chokes. **Actually, RefCounted has a CC, so that is the one that will be used. Just wanted to illustrate it *could* be one of the things that could happen. //-------- Bask on subject, I _have_ started working with invariants. I think they are nice, but there indeed some times where you'd wish they wouldn't trigger. How about the noinvariant function attribute? Sounds like a simple enough solution. At that point, the developer can just insert "assert(&this);" in said functions, if and where he judges it necessary.
Oct 12 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Friday, October 12, 2012 11:31:36 monarch_dodra wrote:
 Bask on subject, I _have_ started working with invariants. I
 think they are nice, but there indeed some times where you'd wish
 they wouldn't trigger.
 
 How about the  noinvariant function attribute? Sounds like a
 simple enough solution.
 
 At that point, the developer can just insert "assert(&this);" in
 said functions, if and where he judges it necessary.

That sounds like a decent solution to me, but I think that there's a good chance that Walter would reject it on principle (since in general, skipping the invariant pretty much defeats the purpose of having one). This is the only case that I'm aware of where it really makes sense to not have an invariant triggered, and he seems to be against the idea that opAssign wouldn't trigger the invariant when called, so I expect that he'd be against this as well if the whole purpose was to enable that case. Other, solid use case would probably be needed as well. - Jonathan M Davis
Oct 12 2012
prev sibling next sibling parent "David Nadlinger" <see klickverbot.at> writes:
On Friday, 12 October 2012 at 09:42:19 UTC, Jonathan M Davis 
wrote:
 That sounds like a decent solution to me, but I think that 
 there's a good
 chance that Walter would reject it on principle (since in 
 general, skipping
 the invariant pretty much defeats the purpose of having one).

But he already suggested implementing a _custom_ mechanism for skipping the invariant somewhere else in this thread (i.e. a "valid" flag) which is arguably even worse… David
Oct 12 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Friday, October 12, 2012 11:49:48 David Nadlinger wrote:
 On Friday, 12 October 2012 at 09:42:19 UTC, Jonathan M Davis
=20
 wrote:
 That sounds like a decent solution to me, but I think that
 there's a good
 chance that Walter would reject it on principle (since in
 general, skipping
 the invariant pretty much defeats the purpose of having one).

But he already suggested implementing a _custom_ mechanism for skipping the invariant somewhere else in this thread (i.e. a "valid" flag) which is arguably even worse=E2=80=A6

Clearly, I missed that. But that's definitely not particularly clean (t= hough it=20 _can_ be done right now without making any changes to the language, whi= ch for=20 most things is the better approach). It's not as bad now that with have= =20 version(assert), since it makes it so that the valid flag can be compil= ed out=20 in release mode, but it's still messier than noinvariant would be (if = nothing=20 else, it requires more code and a version(assert) block every time that= the=20 valid flag is used), and more importantly, it only solves the T.init ca= se and=20 not the case where the struct is initialized to void. So, noinvariant = makes a=20 lot more sense, but it _does_ require an update the language, so I woul= dn't=20 expect Walter to be all that enthused about it, but if he already think= s that=20 a "valid" flag is okay, then he wouldn't necessarily be opposed to the = idea of=20 it being possible to explicitly skip invariant checks in some cases. - Jonathan M Davis
Oct 12 2012
prev sibling next sibling parent "Era Scarecrow" <rtcvb32 yahoo.com> writes:
On Friday, 12 October 2012 at 08:20:42 UTC, Jonathan M Davis 
wrote:

 Really, I think that it's a bad design decision to require that 
 the invariant be called before opAssign. It does _not_ play 
 nice with some of D's other features, and the result is likely 
 to be that invariants get used less, meaning that code is more 
 likely to be buggy.

You make a good argument, but you can also override opAssign for things that are not it's type exact type. So... struct S { float x; ref S opAssign(int y) { x = y; return this; } } S s; int i; s = i; //opAssign, correct? In cases like this opAssign would need an invariant before and after the call. But if you were just replacing the whole object you wouldn't. I'll say a novariant is the better answer, and automatically used on the default copy/opAssign/postblitz (before the call, but still needed after).
Oct 12 2012
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Friday, 12 October 2012 at 20:33:11 UTC, Era Scarecrow wrote:
  I'll say a  novariant is the better answer, and automatically 
 used on the default copy/opAssign/postblitz (before the call, 
 but still needed after).

The language already states that the invariant is only called at the end of construction (ergo copy/postblit). opAssign, IMO, is not (much) different than any other function. invariant checks should be disabled on it, if and when the developer explicitly requests it. That's the safest route anyway.
Oct 12 2012
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Thu, 11 Oct 2012 13:23:10 -0400, Jonathan M Davis <jmdavisProg gmx.com>  
wrote:

 Any situation where the init value is essentially invalid (like it would  
 be
 with floating point types) makes it so that you can't have an invariant,  
 and in
 many of those cases, having a default constructor which was always called
 would solve the problem. I'm still in favor of _not_ trying to add  
 default
 constructors given all of the issues involved, and I agree that on the  
 whole,
 init is a superior solution (even if it isn't perfect), but there _are_  
 cases
 where you can't have an invariant because of it.

Isn't it possible to customize when an "invariant" is called using contracts? For example: struct S { private bool isValid; private void _invariant() {assert(isValid);} void foo() in { _invariant();} out {_invariant();} body { // whatever } void opAssign(ref S other) out {_invariant();} body { isValid = other.isValid; } } ??? Yeah, It's extra work. But essentially, isn't this what you want? The thing about disabling invariant checks on some specific function in some specific case is that someone else has a valid case for requiring it. The only sucky part about the above is, _invariant is compiled in even in release mode (though it should inline to a noop). -Steve
Oct 15 2012
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Monday, 15 October 2012 at 15:56:33 UTC, Steven Schveighoffer 
wrote:
 Isn't it possible to customize when an "invariant" is called 
 using contracts?

 For example:

 struct S
 {
    private bool isValid;
    private void _invariant() {assert(isValid);}

    void foo()
    in { _invariant();} out {_invariant();} body
    {
       // whatever
    }

    void opAssign(ref S other)
    out {_invariant();} body
    {
       isValid = other.isValid;
    }
 }

 ???

 Yeah, It's extra work.  But essentially, isn't this what you 
 want?  The thing about disabling invariant checks on some 
 specific function in some specific case is that someone else 
 has a valid case for requiring it.

 The only sucky part about the above is, _invariant is compiled 
 in even in release mode (though it should inline to a noop).

 -Steve

There is now an "assert" word for version blocks so you can put your code inside that, and it doesn't get compiled in during release.
Oct 15 2012
prev sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Monday, October 15, 2012 11:56:33 Steven Schveighoffer wrote:
 Yeah, It's extra work. But essentially, isn't this what you want? The
 thing about disabling invariant checks on some specific function in some
 specific case is that someone else has a valid case for requiring it.

I've considered it, and I may end up doing that for SysTime, but it's also kind of ridiculous to have to add assertions to _every_ function like that just to avoid having it called on one function.
 The only sucky part about the above is, _invariant is compiled in even in
 release mode (though it should inline to a noop).

version(assert) now fixes that problem.
Oct 15 2012