October 05, 2015
On 05/10/15 10:49, Dmitry Olshansky wrote:

>
> Yes and no, if your type is movable in C++ you have to have a T.init
> equivalent to leave something behind after move (oops!).
No, under C++ you can choose whether it makes sense for your type to be movable or not. No oops at all.

C++ does not make any attempt to protect you from your own incompetence. Within that assumption (which some people accept as reasonable and others don't), C++ does the right thing.

>
> Now if the type is not movable it doesn't really burden much to have an
> invalid state and trigger an assert in dtor to make sure the type is
> properly initialized. Then:
>
> T a; // would trigger assert on dtor
> T a = T(...); // shouldn't
But how will you detect whether the type has been properly constructed or not?

>
> If all states are valid and you still want to guarantee construction
> that gets tricky. For example for "seconds" struct if we allow negative
> seconds as in diff of time. (Though I'd just use long.min for the
> "wrong" flag in this case).
>
But that is a very strong aspect of the C++ paradigm. Saying "you can engineer a type that is valid at any point it is in scope, enforced by the compiler". In fact, it was such a fundamental part of the C++ design that Strastrup had to change a long standing C constraint (variables are only defined at the start of the scope) in order to support it. That change, like many others, has found its way back into C99, so many people today don't even realize it was ever there.

Shachar
October 05, 2015
On 05-Oct-2015 12:33, Shachar Shemesh wrote:
> On 05/10/15 10:49, Dmitry Olshansky wrote:
>
>>
>> Yes and no, if your type is movable in C++ you have to have a T.init
>> equivalent to leave something behind after move (oops!).
> No, under C++ you can choose whether it makes sense for your type to be
> movable or not.

IF you need a movable type. Most types these days ought to be movable to be fast. So it's OOPS after all. e.g. vector<T> is movable hence it has T.init.
*Everything non-trivial* in STL now has T.init, including the unique_ptr and shared_ptr.

The T.init is hidden somewhat:

auto a = unique_ptr<int>(new int);
auto b = move(a);
// now a is unique_ptr<int>.init that is null

Same goes for locks, threads and whatnot.
My point is: if it can move, it has T.init.

Copyable types that are not movable are a minority IMO.

Non-movable were described in the snipped part of my message.

> No oops at all.
>
> C++ does not make any attempt to protect you from your own incompetence.

Sadly it goes to great lengths to prove your own incompetence. Eventually it does, but that is OT.

> Within that assumption (which some people accept as reasonable and
> others don't), C++ does the right thing.

If I got you right you seem to imply that types that are copyable but not movable are a good thing. I humbly disagree.

-- 
Dmitry Olshansky
October 05, 2015
On Monday, 5 October 2015 at 09:25:30 UTC, Shachar Shemesh wrote:
> What's more, init is used even if you @disable this(). The following compile and does what you'd expect (but not what you want):
> struct S {
>    int d;
>
>    @disable this();
>    this( int d ) {
>       this.d = d;
>    }
> }
>
>
> ...
>
>    S d = S.init;

I don't understand this. How is that not what you want, considering that you explicitly told it to use the init value?
October 05, 2015
On Sunday, 4 October 2015 at 21:01:45 UTC, Jonathan M Davis wrote:
> That would be ideal but gets really nasty for a number of reasons - primarily having to do with casting. It's perfectly possible to cast to and from shared and much easier to create something as thread-local and then cast it to shared than to create it as shared, so you very quickly run into problems with objects being created on heap but really needing to be used on another. Another major issue is message passing, because in order to avoid copying, you basically need a way to move an object from one thread-local heap to another. Right now the way you'd do it is to cast to immutable to pass the object and cast it back to mutable on the other side (which is _far_ from ideal). Using shared would be better, but the last time I checked, Variant has issues with it so that that didn't work with std.concurrency. Regardless, the fact that you're casting to pass across threads again runs into issues with objects being created on a particular heap but then needing to be moved to another. _Maybe_ that could be resolved by making casting to and from shared do a lot more work underneath the hood, but that get's _really_ complicated when you start having objects that refer to other objects and the like.

It should at least be considered. I wouldn't want write-barriers, but OTOH cast-barriers might be acceptable.
October 05, 2015
On 05/10/15 13:39, Marc Schütz wrote:
> On Monday, 5 October 2015 at 09:25:30 UTC, Shachar Shemesh wrote:
>> What's more, init is used even if you @disable this(). The following
>> compile and does what you'd expect (but not what you want):
>> struct S {
>>    int d;
>>
>>    @disable this();
>>    this( int d ) {
>>       this.d = d;
>>    }
>> }
>>
>>
>> ...
>>
>>    S d = S.init;
>
> I don't understand this. How is that not what you want, considering that
> you explicitly told it to use the init value?

One of the basics of C++ is that, if you construct the type correctly, then the user of the type cannot use it in a way that will cause inconsistencies. Such a use will not compile.

The above shows that you cannot construct such a type in D. The language simply does not allow you to cancel a certain feature of the type in which you are uninterested.

Please bear in mind that the init might not be called directly. Programmer A defines a type that should never be uninitialized (i.e. - needs a constructor). Programmer B places an instance of that type inside a containing struct. Programmer C uses that containing struct's init. Such a problem is not easy to catch, even if programmers A, B and C are the same person.

Shachar
October 05, 2015
On Monday, 5 October 2015 at 10:57:57 UTC, Shachar Shemesh wrote:
> On 05/10/15 13:39, Marc Schütz wrote:
>> On Monday, 5 October 2015 at 09:25:30 UTC, Shachar Shemesh wrote:
>>> What's more, init is used even if you @disable this(). The following
>>> compile and does what you'd expect (but not what you want):
>>> struct S {
>>>    int d;
>>>
>>>    @disable this();
>>>    this( int d ) {
>>>       this.d = d;
>>>    }
>>> }
>>>
>>>
>>> ...
>>>
>>>    S d = S.init;
>>
>> I don't understand this. How is that not what you want, considering that
>> you explicitly told it to use the init value?
>
> One of the basics of C++ is that, if you construct the type correctly, then the user of the type cannot use it in a way that will cause inconsistencies. Such a use will not compile.
>
> The above shows that you cannot construct such a type in D. The language simply does not allow you to cancel a certain feature of the type in which you are uninterested.

`@disable this();` works fine to prevent the user from doing that accidentally. But in your example, this doesn't happen by accident; you explicitly told the compiler to use the .init value. That's not something you do just because you can, you need to have a reason for it. Presumably a user doing this would know what they're doing.

I don't think it's reasonable to expect to be able to prevent that, just as you can't forbid the user to do unsafe casts. And arguably, if your type's .init is invalid, then "you're doing it wrong".

Besides, that every type has a .init value is a very fundamental part of the language, not just a feature of a particular type. Sure, you use some flexibility for edge cases, but IMO that's a good tradeoff for the general advantages you gain from it.

>
> Please bear in mind that the init might not be called directly. Programmer A defines a type that should never be uninitialized (i.e. - needs a constructor). Programmer B places an instance of that type inside a containing struct. Programmer C uses that containing struct's init. Such a problem is not easy to catch, even if programmers A, B and C are the same person.
>

If you use normal declaration, the compiler correctly rejects that:

struct A { @disable this(); }
struct B { A a; }
void main() { B b; }

// Error: variable xx.main.b default construction is disabled for type B

If you'd use B.init, of course you'd get exactly what you ask for, see above.
October 05, 2015
On Monday, 5 October 2015 at 07:40:35 UTC, deadalnix wrote:
> Not on the heap. There are many cases where the destructor won't run and it is allowed by spec. We should do better.

To be fair, if you new a struct in C++ and never delete it, the destructor will never run there either. D's in the same boat in that regard.
October 05, 2015
On Monday, 5 October 2015 at 11:41:11 UTC, Marc Schütz wrote:
> `@disable this();` works fine to prevent the user from doing that accidentally. But in your example, this doesn't happen by accident; you explicitly told the compiler to use the .init value. That's not something you do just because you can, you need to have a reason for it. Presumably a user doing this would know what they're doing.

Do it by accident:

T safeFront(T[] r)
{
  if(r.length==0)return T.init;
  return r[0];
}
October 07, 2015
On 10/05/2015 12:57 PM, Shachar Shemesh wrote:
> On 05/10/15 13:39, Marc Schütz wrote:
>> On Monday, 5 October 2015 at 09:25:30 UTC, Shachar Shemesh wrote:
>>> What's more, init is used even if you @disable this(). The following
>>> compile and does what you'd expect (but not what you want):
>>> struct S {
>>>    int d;
>>>
>>>    @disable this();
>>>    this( int d ) {
>>>       this.d = d;
>>>    }
>>> }
>>>
>>>
>>> ...
>>>
>>>    S d = S.init;
>>
>> I don't understand this. How is that not what you want, considering that
>> you explicitly told it to use the init value?
>
> One of the basics of C++ is that, if you construct the type correctly,
> then the user of the type cannot use it in a way that will cause
> inconsistencies. Such a use will not compile.
>
> The above shows that you cannot construct such a type in D. The language
> simply does not allow you to cancel a certain feature of the type in
> which you are uninterested.
>
> Please bear in mind that the init might not be called directly.
> Programmer A defines a type that should never be uninitialized (i.e. -
> needs a constructor). Programmer B places an instance of that type
> inside a containing struct. Programmer C uses that containing struct's
> init. Such a problem is not easy to catch, even if programmers A, B and
> C are the same person.
>
> Shachar

struct S{
    @disable this();
    @disable enum init=0;
}

void main(){
    S s; // error
    auto d=S.init; // error
}

October 07, 2015
On Wednesday, 7 October 2015 at 09:59:05 UTC, Timon Gehr wrote:
> struct S{
>     @disable this();
>     @disable enum init=0;
> }
>
> void main(){
>     S s; // error
>     auto d=S.init; // error
> }

That's just awful.