Thread overview
Bypassing const with a union
Jun 01, 2012
Era Scarecrow
Jun 01, 2012
Era Scarecrow
Jun 01, 2012
Ali Çehreli
Jun 01, 2012
Era Scarecrow
Jun 01, 2012
Dmitry Olshansky
Jun 01, 2012
Era Scarecrow
Jun 02, 2012
Dmitry Olshansky
June 01, 2012
 While working on the BitArray I've come across an interesting dilemma, which is both bad and good. Let's take the following

struct S {
  size_t[] array;
}

 Simple enough, however make a const function, and suddenly you can't return a copy of it.

  S copy() const {
    return this; //compile-time error
  }

 Not bad, type system is doing it's job. Let's say I want to adjust the pointer as a slice

  S opSlice(int s, int e) { //non-const-able call
    S sl;
    sl.array = this.array[s .. e];
    return sl;
  }

 Good, but if I wanted to return an const item (as above) we have an issue. Since I want to change the pointer (but not it's contents) as a slice, the const system gets in the way without duping it.

  //from const, to const
  const(S) opSlice(int s, int e) const {
    S s = this;  //compile error this.array is const
    s.array = this[s .. e]; //compile error
    return s;
  }

  The item starts as const and ends as const and no data (other than maybe the struct which is new) changes; we should be able to adjust the pointer if we want. If I use a union I can bypass this.

struct S {
  union {
    size_t[] array;
    size_t[array.sizeof / size_t.sizeof] nonConst; //since it's a fixed array
  }

  const(S) opSlice(int s, int e) const {
    S sl;
    sl.nonConst = nonConst; //bypass const!
    sl.array = sl.array[s .. e];
    return sl;
  }
}

 Part of the simpler solutions go away when I try to const a struct when cast's have already been declared as with BitArray's original design. Meaning I couldn't just do this:

 return const(BitArray) s; //template not found for opCast(T) or something

 Would I use const within the cast?

  const(BitArray) opCast(T : BitArray)() { //or something similar?
    return const(BitArray) b; //infinite loop?
  }



 Although this means it could be quite dangerous, used in the right way it can also be a good thing. However consider this:

union X {
  string str;
  char[] chr;
}

char[] nonConstString(string inString){
  X x;
  x.str = inString;
  return x.chr; //immutable becomes non-immutable?
}

void bomb() {
  auto ncs = nonConstString("Go Boom!");
  ncs[0] = '!'; //should blow up or seg fault?
  writeln(ncs); //in my own test it silently succeeds
}

 Thinking about it, although this is possible you might still allow it but send warnings when the compiler detects it. Reason? Const takes effect too soon, sometimes before you can finish working on the changes. I've tried moving them to a new constructor(s) but keep having issues when the inputs are const as the struct assumes it's starting non-const. I think...

 Breaking the const system while your still building/preparing the new object should be allowed (as with the slice example) but once you pass it out it shouldn't be allowed anymore.
June 01, 2012
On Friday, 1 June 2012 at 20:24:39 UTC, Era Scarecrow wrote:
>   //from const, to const
>   const(S) opSlice(int s, int e) const {
>     S s = this;  //compile error this.array is const
>     s.array = this[s .. e]; //compile error
>     return s;
>   }

Correction: Seems this didn't get updated while I was testing this.

   //from const, to const
   const(S) opSlice(int s, int e) const {
     S sl = this;  //compile error this.array is const
     sl.array = this[s .. e]; //compile error
     return sl;
   }
June 01, 2012
On 06/01/2012 01:24 PM, Era Scarecrow wrote:

> Good, but if I wanted to return an const item (as above) we have an
> issue. Since I want to change the pointer (but not it's contents) as a
> slice, the const system gets in the way without duping it.
>
> //from const, to const
> const(S) opSlice(int s, int e) const {
> S s = this; //compile error this.array is const
> s.array = this[s .. e]; //compile error
> return s;
> }

Don't forget inout, which seems to be working at least in this case:

import std.traits;
import std.stdio;

struct S {
    size_t[] array;

    void length(size_t length) @property
    {
        array.length = length;
    }

    inout(S) copy() inout {
        return this;
    }

    inout(S) opSlice(int s, int e) inout {
        Unqual!S sl;
        sl.array = (cast(Unqual!S)this).array[s .. e];
        return cast(inout(S))sl;
    }
}


void main()
{
    auto s = S();
    s.length = 10;

    const s3 = s[0..8];
    const s4 = s3[1..3];

    auto sc = s.copy();
    auto s3c = s3.copy();
}

std.traits.Unqual seems like a dirty trick there but I see it being used in Phobos as well.

> struct S {
> union {
> size_t[] array;
> size_t[array.sizeof / size_t.sizeof] nonConst; //since it's a fixed array
> }

I don't understand the calculation there. array.sizeof and size_t.sizeof are not related in that way. It works only if 'array' is fixed-length as well.

Ali

-- 
D Programming Language Tutorial: http://ddili.org/ders/d.en/index.html

June 01, 2012
On Friday, 1 June 2012 at 21:15:18 UTC, Ali Çehreli wrote:
> Don't forget inout, which seems to be working at least in this case:

 I'd given up on trying to use inout, being as it's confusing to try and use, when I try to use it it, it always complains. Perhaps better documentation/examples or I gotta look at it all over again. To my understanding you had to have inout in the signature and as a input parameter (which no input arguments never fit). Hmm..

> import std.traits;
> import std.stdio;
>
> struct S {
>     size_t[] array;
>
>     void length(size_t length) @property
>     {
>         array.length = length;
>     }
>
>     inout(S) copy() inout {
>         return this;
>     }
>
>     inout(S) opSlice(int s, int e) inout {
>         Unqual!S sl;
>         sl.array = (cast(Unqual!S)this).array[s .. e];
>         return cast(inout(S))sl;
>     }
> }
>
>
> void main()
> {
>     auto s = S();
>     s.length = 10;
>
>     const s3 = s[0..8];
>     const s4 = s3[1..3];
>
>     auto sc = s.copy();
>     auto s3c = s3.copy();
> }

 I'll try and use that, although Unqual is new to me. When going through the library some of it makes sense and a larger portion doesn't.

> std.traits.Unqual seems like a dirty trick there but I see it being used in Phobos as well.
>
> > struct S {
> > union {
> > size_t[] array;
> > size_t[array.sizeof / size_t.sizeof] nonConst; //since it's a
> fixed array
> > }
>
> I don't understand the calculation there. array.sizeof and size_t.sizeof are not related in that way. It works only if 'array' is fixed-length as well.

 The calculation is rather simple, but not so obvious. Convert to the size in bytes, then convert that size to how many words needed for the fixed array to fit. Had array been something a bit bigger, it would still work (assuming it's evenly divisible). Course you could always just include a magic number :P.
June 01, 2012
On 02.06.2012 0:24, Era Scarecrow wrote:
> While working on the BitArray I've come across an interesting dilemma,
> which is both bad and good. Let's take the following

[snip]

> Thinking about it, although this is possible you might still allow it
> but send warnings when the compiler detects it. Reason? Const takes
> effect too soon, sometimes before you can finish working on the changes.
> I've tried moving them to a new constructor(s) but keep having issues
> when the inputs are const as the struct assumes it's starting non-const.
> I think...
>

There is also cast() that just cancels out all const/shared/immutable.

> Breaking the const system while your still building/preparing the new
> object should be allowed (as with the slice example)

Yes in constructor. Or by constructing incrementally a mutable object, inside pure function e.g. compiler can convert to immutable on return (auto-magically).

 but once you pass
> it out it shouldn't be allowed anymore.


-- 
Dmitry Olshansky
June 01, 2012
On Friday, 1 June 2012 at 23:14:14 UTC, Dmitry Olshansky wrote:
> There is also cast() that just cancels out all const/shared/immutable.

 Only the first level, transitive const/immutable don't go away in my experience. Perhaps I'm doing it wrong, or perhaps it's just a protective feature to protect the lower levels so you don't get C++'s const system.

>> Breaking the const system while your still building/preparing the new object should be allowed (as with the slice example)
>
> Yes in constructor. Or by constructing incrementally a mutable object, inside pure function e.g. compiler can convert to immutable on return (auto-magically).

 Which is sometimes where I'm getting stuck. In the constructor it complains about not convertible from const to mutable even if the object being passed back will be const/immutable.

 In my limited experience where it is emulating a slice I would need an exact copy of the struct and then modify what I need before passing it back; cast doesn't do the job, and manually copying const objects to non-const is an annoyance or a pain in it's own regard.

June 02, 2012
On 02.06.2012 3:28, Era Scarecrow wrote:
> On Friday, 1 June 2012 at 23:14:14 UTC, Dmitry Olshansky wrote:
>> There is also cast() that just cancels out all const/shared/immutable.
>
> Only the first level, transitive const/immutable don't go away in my
> experience. Perhaps I'm doing it wrong, or perhaps it's just a
> protective feature to protect the lower levels so you don't get C++'s
> const system.
>
>>> Breaking the const system while your still building/preparing the new
>>> object should be allowed (as with the slice example)
>>
>> Yes in constructor. Or by constructing incrementally a mutable object,
>> inside pure function e.g. compiler can convert to immutable on return
>> (auto-magically).
>
> Which is sometimes where I'm getting stuck. In the constructor it
> complains about not convertible from const to mutable even if the object
> being passed back will be const/immutable.
>

Mmm IRC you can assign each field in const constructor only once. After that it's cooked and treated as const from now on.

> In my limited experience where it is emulating a slice I would need an
> exact copy of the struct and then modify what I need before passing it
> back; cast doesn't do the job, and manually copying const objects to
> non-const is an annoyance or a pain in it's own regard.


Sure it is.

-- 
Dmitry Olshansky
June 25, 2012
On Fri, 01 Jun 2012 18:51:54 -0400, Era Scarecrow <rtcvb32@yahoo.com> wrote:

> On Friday, 1 June 2012 at 21:15:18 UTC, Ali Çehreli wrote:
>> Don't forget inout, which seems to be working at least in this case:
>
>   I'd given up on trying to use inout, being as it's confusing to try and use, when I try to use it it, it always complains. Perhaps better documentation/examples or I gotta look at it all over again. To my understanding you had to have inout in the signature and as a input parameter (which no input arguments never fit). Hmm..

inout is not completely fleshed out yet.  It's turning out to be quite a complex problem to solve, but it's getting there.

>
>> import std.traits;
>> import std.stdio;
>>
>> struct S {
>>     size_t[] array;
>>
>>     void length(size_t length) @property
>>     {
>>         array.length = length;
>>     }
>>
>>     inout(S) copy() inout {
>>         return this;
>>     }
>>
>>     inout(S) opSlice(int s, int e) inout {
>>         Unqual!S sl;
>>         sl.array = (cast(Unqual!S)this).array[s .. e];
>>         return cast(inout(S))sl;
>>     }
>> }

I think this will work for slicing:

inout(S) opSlice(int s, int e) inout {
   return inout(S)(array[s..e]);
}

I use this quite a bit in dcollections.  This seems more attractive to me to any other solution, especially if casting is involved.

-Steve