October 11, 2012
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
October 11, 2012
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
October 11, 2012
On 2012-10-11, 15:52, deadalnix wrote:

> 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.

But not all functions are CTFE-able, so it's not a solution in all cases.

-- 
Simen
October 11, 2012
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.
October 11, 2012
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
October 11, 2012
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.
October 11, 2012
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.

October 11, 2012
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.

October 11, 2012
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
October 11, 2012
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.