Thread overview
Remaining const niggles #1 - Custom POD types
Feb 17, 2008
Janice Caron
Feb 17, 2008
Christopher Wright
Feb 17, 2008
Janice Caron
Feb 18, 2008
Neil Vice
Feb 18, 2008
Christopher Wright
Feb 18, 2008
Janice Caron
Feb 18, 2008
Christopher Wright
Feb 18, 2008
Janice Caron
Feb 18, 2008
Walter Bright
Feb 18, 2008
Janice Caron
February 17, 2008
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.
February 17, 2008
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).

February 17, 2008
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.
February 18, 2008
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.


February 18, 2008
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
>> 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

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.
February 18, 2008
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.
February 18, 2008
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.
>> 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.

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.
February 18, 2008
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.
February 18, 2008
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).
February 18, 2008
> Structs will be implicitly convertible to/from invariant if each of its fields can be (even private fields).

Woo hoo!

Thank you.