www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Remaining const niggles #1 - Custom POD types

reply "Janice Caron" <caron800 googlemail.com> writes:
On the whole, I'm very happy with the new const system. However, there
are one or two niggles which remain, and I feel that enough time has
passed that it's safe to start bringing them up for discussion. So
here's my number one complaint. This works:

    string s = "hello";
    char c = s[0];

Nothing unusual there, you might say. The problem is that the
following /doesn't/ work (as in, won't compile!)

    typedef char mychar;
    alias invariant(mychar)[] mystring;

    mystring s = cast(mystring)"hello";
    mychar c = s[0];

The final line won't compile. The compiler, apparently, cannot
implicitly convert an invariant(mychar) to a mychar. But why not? It
can do that for char, and mychar is just a typedef for char. Changing
mychar from a typedef to a struct wrapper doesn't fix things either.

But I believe there is a solution.

The fix is, the compiler must allow const(T) and invariant(T) both to
implicitly cast to T, if and only if T is "pod". (Plain Old Data).
This is a very simple thing to define. A type T is pod if and only if
one of the following is true:

    T is a primitive type
    T is a typedef for a pod
    T is a struct in which every member variable is pod
    T is a union in which every member variable is pod

In anticipation of criticism, let me fire the first shot against this
idea myself! The problem is that whether or not a struct is pod, may
end up depending on private member variables. That means that the user
of a struct who has only the published API to go on, might assume that
a struct is pod (because all published member variables are pod), but
be unaware that the struct has private pointers.

There are two ways to deal with this criticism. The first way is to
say "Who cares?". We should all assume that types are going to be
non-pod unless they are explicitly documented as being so. This is my
favorite answer.

The second way is to decorate struct and union declarations which are
pod, so that (a) the compiler can see at a glance whether or not the
struct is pod, and (b) the podness or not of a struct appears in its
API. The word "static" could probably be reused here to denote podness
- for example:

    struct S
    {
        int * p; /* OK - S is not pod */
    }

but

    static struct T
    {
        int * p; /* ERROR - T is pod */
    }

Under this regime, the declaration of T wouldn't even compile, forcing
the writer to remove either the member variable p, or the word
"static". Without the word static, const(T) and invariant(T) will not
implicitly cast to T.

For those who don't like the reuse of the word "static", it's not the
only possible syntax. One could invent a new keyword (e.g. "pod"), or
even a new syntax (e.g. "struct(pod)" instead of "struct", which
achieves the same thing without a new keyword.)

The point is, it's the feature that matters, not the syntax of it. The
two approaches are: (a) no decoration - let the compiler figure it
out, or (b) decoration - helps everyone, but adds slightly more
surface area to the const system.

Whichever route we go, I absolutely would like to see this one
addressed. I have a good use for that mychar type I mentioned at the
start of the post. I have already submitted the example I used as a
bug in bugzilla, so we'll see what happens, but I thought I'd open up
the discussion here, to get some feel for what we D2 users think is
the best way to fix this.
Feb 17 2008
next sibling parent reply Christopher Wright <dhasenan gmail.com> writes:
Janice Caron wrote:
 On the whole, I'm very happy with the new const system. However, there
 are one or two niggles which remain, and I feel that enough time has
 passed that it's safe to start bringing them up for discussion. So
 here's my number one complaint. This works:
 
     string s = "hello";
     char c = s[0];
 
 Nothing unusual there, you might say. The problem is that the
 following /doesn't/ work (as in, won't compile!)
 
     typedef char mychar;
     alias invariant(mychar)[] mystring;
 
     mystring s = cast(mystring)"hello";
     mychar c = s[0];
 
 The final line won't compile. The compiler, apparently, cannot
 implicitly convert an invariant(mychar) to a mychar. But why not? It
 can do that for char, and mychar is just a typedef for char. Changing
 mychar from a typedef to a struct wrapper doesn't fix things either.
 
 But I believe there is a solution.
 
 The fix is, the compiler must allow const(T) and invariant(T) both to
 implicitly cast to T, if and only if T is "pod". (Plain Old Data).
 This is a very simple thing to define. A type T is pod if and only if
 one of the following is true:
 
     T is a primitive type
     T is a typedef for a pod

These two are fine. A typedef should work like the original type. I'm curious as to how typedefs and automatically casting from invariant(char) to char are handled.
     T is a struct in which every member variable is pod
     T is a union in which every member variable is pod

These two are more troublesome. You could define an opImplicitCast that unconsts the struct, though that might involve an additional copy (or it might not).
Feb 17 2008
parent reply Christopher Wright <dhasenan gmail.com> writes:
Janice Caron wrote:
 On 17/02/2008, Christopher Wright <dhasenan gmail.com> wrote:
     T is a struct in which every member variable is pod
     T is a union in which every member variable is pod


It's a /definition/. How can a /definition/ be troublesome. (Though actually, it was incomplete. I complete it with the revised definition below. I define a type T to be pod, if and only if one of the following conditions apply: T is a primitive type T is a typedef for a pod T is a struct in which every member variable is pod T is a union in which every member variable is pod T is const(U), for some U, where U is pod T is invariant(U), for some U, where U is pod

I don't want to have to remember all that.
 My definition is unambiguous, and succeeds in defining every single
 type as either pod or not-pod. This is in no way troublesome.
 
 
 You could define an opImplicitCast that
 unconsts the struct, though that might involve an additional copy (or it
 might not).

One cannot "unconst" something without an explicit cast. This is precisely what I aim to avoid. The explicit cast should be unnecessary, whether within opImplicitCast (or any other function), or not.

Yes you can: struct Something { int member; //... Something opImplicitCast() const { Something s; s.member = this.member; return member; } } And if there's something you should do when changing from const to non-const, you can take care of that as well.
 Explicitly unconsting something with a cast is dangerous. It's
 something I'd like to see avoided. (I could easily get my mystring
 example to work, if I allowed myself explicit casts).

Whoever designed the struct can tell you whether it's safe to cast a const form of it to a mutable form.
 What you're suggesting is putting an /explicit/ cast inside an
 op/implicit/Cast function. That just strikes me as scary.

So use it with caution. You're writing the struct, you should know whether it's safe.
 More to the point, one should not need to do
 
     struct MyPODStruct
     {
         MyPODStruct opImplicitCast() const
         {
             return cast(MyPODStruct)(*this);
         }
     }
 
 for every single pod struct you create. It is the const type system
 which is at fault here - not the struct. It is the const type system
 which needs to be addressed, and it should not be the struct writer's
 job to find a workaround.

You need to tell the compiler whether it's okay to copy a const struct into a mutable one. I don't have a problem with that, and you don't. You seem to come across the issue often enough that you want to vent about it, but you could write a template to do it easily enough. I'm not writing the code that you are, so I don't see this issue often enough to consider it necessary to introduce a language feature to get const structs that you can implicitly cast to mutable ones. The only issue I see is that you can't currently overload opImplicitCast. However, that feature will be added at some point, probably sooner than any language feature you propose today.
Feb 17 2008
parent Christopher Wright <dhasenan gmail.com> writes:
Janice Caron wrote:
 On 18/02/2008, Christopher Wright <dhasenan gmail.com> wrote:
 One cannot "unconst" something without an explicit cast. This is
 precisely what I aim to avoid. The explicit cast should be
 unnecessary, whether within opImplicitCast (or any other function), or
 not.

struct Something { int member; //... Something opImplicitCast() const { Something s; s.member = this.member; return member; } }

Ah, but you're not thinking ahead. Right now, that may well work, but in the future, when the "struct const bug" is fixed, it will stop working. You're basically exploiting a bug there.

No, I'm forgetting the transitive nature of const.
 It's like this - if s is an instance of const(S), then, not only is s
 const, but /every member of s is also (transitively) const. Right now,
 the compiler doesn't know that, because it's a bug, but it will be
 fixed before too long. (It has to be, as it allows one to break
 constancy). Once that bug is fixed, you will no longer be able to do
 
     s.member = this.member;
 
 because this.member will have type const(U), for some U, while
 s.member will have type U, and (...and this is the whole point of this
 thread...) const(U) will not implicitly cast to U. So sure, your
 example works today. But it most likely will not work tomorrow. The
 only way to guarantee that it will work post-bug-fix would be to write
 
     s.member = cast(something)(this.member);
 
 instead.

True, unless your members all have a defined opImplicitCast to break const or are primitives. And unless they do, you don't know if it's safe to convert the containing type, so you shouldn't. So my strategy works as long as you add the code to all your structs.
 Whoever designed the struct can tell you whether it's safe to cast a
 const form of it to a mutable form.

They can tell you their /intent/, but only the compiler can absolutely guarantee it. The compiler can /prove/ whether or not a given type is safe to copy.

True. There's value in that.
 You need to tell the compiler whether it's okay to copy a const struct
 into a mutable one.

First off, let me remind everyone that casting away const is undefined in D. Let me stress that point - /undefined/. That doesn't mean "use with caution", it means you should never, ever do it, except to call library functions which have been incorrectly declared.

opImplicitCast allows you to define a copy mechanism from a const UDT to a non-const UDT, as long as you can copy every member from a const one to a non-const one. This isn't casting away const; it's copying. You could call it dup() instead and have everything work, except for explicitly calling the method.
Feb 18 2008
prev sibling next sibling parent reply "Janice Caron" <caron800 googlemail.com> writes:
On 17/02/2008, Christopher Wright <dhasenan gmail.com> wrote:
     T is a struct in which every member variable is pod
     T is a union in which every member variable is pod

These two are more troublesome.

It's a /definition/. How can a /definition/ be troublesome. (Though actually, it was incomplete. I complete it with the revised definition below. I define a type T to be pod, if and only if one of the following conditions apply: T is a primitive type T is a typedef for a pod T is a struct in which every member variable is pod T is a union in which every member variable is pod T is const(U), for some U, where U is pod T is invariant(U), for some U, where U is pod My definition is unambiguous, and succeeds in defining every single type as either pod or not-pod. This is in no way troublesome.
 You could define an opImplicitCast that
 unconsts the struct, though that might involve an additional copy (or it
 might not).

One cannot "unconst" something without an explicit cast. This is precisely what I aim to avoid. The explicit cast should be unnecessary, whether within opImplicitCast (or any other function), or not. Explicitly unconsting something with a cast is dangerous. It's something I'd like to see avoided. (I could easily get my mystring example to work, if I allowed myself explicit casts). What you're suggesting is putting an /explicit/ cast inside an op/implicit/Cast function. That just strikes me as scary. More to the point, one should not need to do struct MyPODStruct { MyPODStruct opImplicitCast() const { return cast(MyPODStruct)(*this); } } for every single pod struct you create. It is the const type system which is at fault here - not the struct. It is the const type system which needs to be addressed, and it should not be the struct writer's job to find a workaround.
Feb 17 2008
parent "Neil Vice" <sardonicpresence gmail.com> writes:
I could not possibly agree with you more Janice.

Being new to D2 and having missed all the const discussions I've generally 
been disappointed with the const system. Having said that my major gripes 
are the constant need for explicit casts (as you have covered) as well as 
lack of support in operator overloading in particular (e.g. opApply), which 
I hear is remoured to be being addressed currently. 
Feb 17 2008
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
On 18/02/2008, Christopher Wright <dhasenan gmail.com> wrote:
 One cannot "unconst" something without an explicit cast. This is
 precisely what I aim to avoid. The explicit cast should be
 unnecessary, whether within opImplicitCast (or any other function), or
 not.

Yes you can: struct Something { int member; //... Something opImplicitCast() const { Something s; s.member = this.member; return member; } }

Ah, but you're not thinking ahead. Right now, that may well work, but in the future, when the "struct const bug" is fixed, it will stop working. You're basically exploiting a bug there. It's like this - if s is an instance of const(S), then, not only is s const, but /every member of s is also (transitively) const. Right now, the compiler doesn't know that, because it's a bug, but it will be fixed before too long. (It has to be, as it allows one to break constancy). Once that bug is fixed, you will no longer be able to do s.member = this.member; because this.member will have type const(U), for some U, while s.member will have type U, and (...and this is the whole point of this thread...) const(U) will not implicitly cast to U. So sure, your example works today. But it most likely will not work tomorrow. The only way to guarantee that it will work post-bug-fix would be to write s.member = cast(something)(this.member); instead.
 Whoever designed the struct can tell you whether it's safe to cast a
 const form of it to a mutable form.

They can tell you their /intent/, but only the compiler can absolutely guarantee it. The compiler can /prove/ whether or not a given type is safe to copy.
 You need to tell the compiler whether it's okay to copy a const struct
 into a mutable one.

First off, let me remind everyone that casting away const is undefined in D. Let me stress that point - /undefined/. That doesn't mean "use with caution", it means you should never, ever do it, except to call library functions which have been incorrectly declared. The compiler knows (as in, can prove), what is safe and what isn't. It is therefore the compiler's job to do that. It is absolutely not a good idea to tell programmers that they need to start resorting to constructions with undefined behaviour.
Feb 17 2008
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
On 18/02/2008, Christopher Wright <dhasenan gmail.com> wrote:
 opImplicitCast allows you to define a copy mechanism from a const UDT to
 a non-const UDT,

That actually does seem a bit mad though. I mean - not your idea - just having to do it. Can you even /imagine/ having to do that in C++! Imagine having to expain it to a potential convert: "Yes, I gave my struct S an implicit casting function which returns ... er ... another S. What do you mean: Why can't it do that all by itself!?". Having to do that explicitly, in every single struct you make ... well, honestly it just makes the D programming language look a bit pathetic. This is not a difficult thing for Walter et al to fix. The rules I came up with earlier weren't meant for humans - they were a first stab at rules for the compiler. But here's the easier to understand rule, the principle, so to speak: "If the compiler can prove that copying is safe, allow the copy". Really, that's the only rule I'm asking for.
Feb 18 2008
prev sibling next sibling parent Walter Bright <newshound1 digitalmars.com> writes:
Janice Caron wrote:
 The problem is that the
 following /doesn't/ work (as in, won't compile!)
 
     typedef char mychar;
     alias invariant(mychar)[] mystring;
 
     mystring s = cast(mystring)"hello";
     mychar c = s[0];

That's a bug and will get fixed on the next update. The rule is that a typedef can be implicitly converted to/from invariant if the underlying type can be. Structs will be implicitly convertible to/from invariant if each of its fields can be (even private fields).
Feb 18 2008
prev sibling parent "Janice Caron" <caron800 googlemail.com> writes:
 Structs will be implicitly convertible to/from invariant if each of its
 fields can be (even private fields).

Woo hoo! Thank you.
Feb 18 2008