July 21, 2010
Dmitry Olshansky:
> The problem is designing such classes and then documenting: "you should always use it as 'scope' ", is awkward.

If you really want a class to be used as scope only you can do this, see the error message:

scope class Foo {}
void main() {
  Foo f = new Foo;
}



> The second writefln prints garbage. I guess it's because of pointer to the long gone stackframe, which is ovewritten by the first writeln.

Yes scope has this and other problems (and I think two of them can be fixed), but I don't think emplace() is a big improvement.

Bye,
bearophile
July 21, 2010
On 21.07.2010 16:26, bearophile wrote:
> Dmitry Olshansky:
>    
>> The problem is designing such classes and then documenting: "you should
>> always use it as 'scope' ", is awkward.
>>      
> If you really want a class to be used as scope only you can do this, see the error message:
>
> scope class Foo {}
> void main() {
>    Foo f = new Foo;
> }
>
>
>    
>> The second writefln prints garbage. I guess it's because of pointer to
>> the long gone stackframe, which is ovewritten by the first writeln.
>>      
> Yes scope has this and other problems (and I think two of them can be fixed), but I don't think emplace() is a big improvement.
>
> Bye,
> bearophile
>    
Going further with library implementation as opposed to language feature, I made a (somewhat) successful try at implementing scoped classes:

struct Scoped(T){
    ubyte[__traits(classInstanceSize, Test)] _payload;
    T getPayload(){
        return cast(T)(_payload.ptr);
    }
    alias getPayload this;
    static Scoped opCall(Args...)(Args args) if ( is(typeof(T.init.__ctor(args))) ){// TODO: should also provide decent error message
        Scoped!T s;
        emplace!T(cast(void[])s._payload,args);
        return s;
    }
    ~this(){
        clear(getPayload);
    }
}

now replace the orignal while loop with this:
while (i < N) {
        auto testObject = Scoped!Test(i, i, i, i, i, i);
        //assuming we have aforementioned evil function func(Test t), that keeps global reference to t.
        //fun(testObject); //uncoment to get an compile error - type mismatch
        testObject.doSomething(i, i, i, i, i, i);
        testObject.doSomething(i, i, i, i, i, i);
        testObject.doSomething(i, i, i, i, i, i);
        testObject.doSomething(i, i, i, i, i, i);
        i++;
    }

and all works just the same as with deprecated scope storage class.
Even better it disallows passing the variable to functions expecting vanilla Test, it's limiting but for a good reason.
There are still issues that should be solved (name clash for one, plus the ability to define default construct Scoped!T) but overall it's OK to me.

-- 
Dmitry Olshansky

July 21, 2010
Dmitry Olshansky wrote:
> On 21.07.2010 16:26, bearophile wrote:
>> Dmitry Olshansky:
>>   
>>> The problem is designing such classes and then documenting: "you should
>>> always use it as 'scope' ", is awkward.
>>>      
>> If you really want a class to be used as scope only you can do this, see the error message:
>>
>> scope class Foo {}
>> void main() {
>>    Foo f = new Foo;
>> }
>>
>>
>>   
>>> The second writefln prints garbage. I guess it's because of pointer to
>>> the long gone stackframe, which is ovewritten by the first writeln.
>>>      
>> Yes scope has this and other problems (and I think two of them can be fixed), but I don't think emplace() is a big improvement.
>>
>> Bye,
>> bearophile
>>    
> Going further with library implementation as opposed to language feature, I made a (somewhat) successful try at implementing scoped classes:
> 
> struct Scoped(T){
>     ubyte[__traits(classInstanceSize, Test)] _payload;
>     T getPayload(){
>         return cast(T)(_payload.ptr);
>     }
>     alias getPayload this;
>     static Scoped opCall(Args...)(Args args) if ( is(typeof(T.init.__ctor(args))) ){// TODO: should also provide decent error message
>         Scoped!T s;
>         emplace!T(cast(void[])s._payload,args);
>         return s;
>     }
>     ~this(){
>         clear(getPayload);
>     }
> }
> 
> now replace the orignal while loop with this:
> while (i < N) {
>         auto testObject = Scoped!Test(i, i, i, i, i, i);
>         //assuming we have aforementioned evil function func(Test t), that keeps global reference to t.
>         //fun(testObject); //uncoment to get an compile error - type mismatch
>         testObject.doSomething(i, i, i, i, i, i);
>         testObject.doSomething(i, i, i, i, i, i);
>         testObject.doSomething(i, i, i, i, i, i);
>         testObject.doSomething(i, i, i, i, i, i);
>         i++;
>     }
> 
> and all works just the same as with deprecated scope storage class.
> Even better it disallows passing the variable to functions expecting vanilla Test, it's limiting but for a good reason.
> There are still issues that should be solved (name clash for one, plus the ability to define default construct Scoped!T) but overall it's OK to me.
> 

Nice work. To avoid name clashes with alias this, you may want to use a
trick invented by Shin Fujishiro:

struct ExWhyZee {
   template ExWhyZee() {
      // implementation goes here
   }
   alias ExWhyZee!().whatever this;
}

This way you never have a confusion between the symbols defined by the
wrapped type and your own type. I use a variant of the same trick in
RefCounted.


Andrei
July 21, 2010
Dmitry Olshansky wrote:
> On 21.07.2010 16:26, bearophile wrote:
>> Dmitry Olshansky:
>>   
>>> The problem is designing such classes and then documenting: "you should
>>> always use it as 'scope' ", is awkward.
>>>      
>> If you really want a class to be used as scope only you can do this, see the error message:
>>
>> scope class Foo {}
>> void main() {
>>    Foo f = new Foo;
>> }
>>
>>
>>   
>>> The second writefln prints garbage. I guess it's because of pointer to
>>> the long gone stackframe, which is ovewritten by the first writeln.
>>>      
>> Yes scope has this and other problems (and I think two of them can be fixed), but I don't think emplace() is a big improvement.
>>
>> Bye,
>> bearophile
>>    
> Going further with library implementation as opposed to language feature, I made a (somewhat) successful try at implementing scoped classes:

I salute this approach.

> struct Scoped(T){
>     ubyte[__traits(classInstanceSize, Test)] _payload;

s/Test/T/ I suppose.

>     T getPayload(){
>         return cast(T)(_payload.ptr);
>     }
>     alias getPayload this;
>     static Scoped opCall(Args...)(Args args) if ( is(typeof(T.init.__ctor(args))) ){// TODO: should also provide decent error message
>         Scoped!T s;
>         emplace!T(cast(void[])s._payload,args);
>         return s;
>     }
>     ~this(){
>         clear(getPayload);
>     }
> }
> 
> now replace the orignal while loop with this:
> while (i < N) {
>         auto testObject = Scoped!Test(i, i, i, i, i, i);
>         //assuming we have aforementioned evil function func(Test t), that keeps global reference to t.
>         //fun(testObject); //uncoment to get an compile error - type mismatch
>         testObject.doSomething(i, i, i, i, i, i);
>         testObject.doSomething(i, i, i, i, i, i);
>         testObject.doSomething(i, i, i, i, i, i);
>         testObject.doSomething(i, i, i, i, i, i);
>         i++;
>     }
> 
> and all works just the same as with deprecated scope storage class.
> Even better it disallows passing the variable to functions expecting vanilla Test, it's limiting but for a good reason.
> There are still issues that should be solved (name clash for one, plus the ability to define default construct Scoped!T) but overall it's OK to me.

I agree Scope has a rightful place in the standard library.


Andrei
July 21, 2010
Dmitry Olshansky wrote:

> now replace the orignal while loop with this:
> while (i < N) {
> auto testObject = Scoped!Test(i, i, i, i, i, i);
> //assuming we have aforementioned evil function func(Test t),
> that keeps global reference to t.
> //fun(testObject); //uncoment to get an compile error - type
> mismatch
> testObject.doSomething(i, i, i, i, i, i);
> testObject.doSomething(i, i, i, i, i, i);
> testObject.doSomething(i, i, i, i, i, i);
> testObject.doSomething(i, i, i, i, i, i);
> i++;
> }

With your code I `time` reports the below timings on my machine:
real	0m19.658s
user	0m19.590s
sys	0m0.010s

compared to:
real	0m9.122s
user	0m9.090s
sys	0m0.000s

with bearofiles original version. With -O -release its about 4 seconds faster for each.
July 21, 2010
On 07/21/2010 01:59 PM, Rory Mcguire wrote:
> Dmitry Olshansky wrote:
>
>> now replace the orignal while loop with this:
>> while (i<  N) {
>> auto testObject = Scoped!Test(i, i, i, i, i, i);
>> //assuming we have aforementioned evil function func(Test t),
>> that keeps global reference to t.
>> //fun(testObject); //uncoment to get an compile error - type
>> mismatch
>> testObject.doSomething(i, i, i, i, i, i);
>> testObject.doSomething(i, i, i, i, i, i);
>> testObject.doSomething(i, i, i, i, i, i);
>> testObject.doSomething(i, i, i, i, i, i);
>> i++;
>> }
>
> With your code I `time` reports the below timings on my machine:
> real	0m19.658s
> user	0m19.590s
> sys	0m0.010s
>
> compared to:
> real	0m9.122s
> user	0m9.090s
> sys	0m0.000s
>
> with bearofiles original version. With -O -release its about 4 seconds
> faster for each.

I compiled and ran the tests myself with -O -release -inline and got 1.95s for Dmitry's implementation and 1.55s for bearophile's.

I optimized Dmitry's implementation in two ways: I replaced the call to clear() with a straight call to the destructor and added = void in two places to avoid double initialization. I got 1.11s, which significantly undercuts the implementation using scope.

Here's the code I used for testing:

struct Scoped(T) {
    ubyte[__traits(classInstanceSize, Test)] _payload = void;
    T getPayload(){
        return cast(T)(_payload.ptr);
    }
    alias getPayload this;

    static Scoped opCall(Args...)(Args args)
    if (is(typeof(T.init.__ctor(args)))) {
        // TODO: should also provide decent error message
        Scoped!T s = void;
        emplace!T(cast(void[])s._payload,args);
        return s;
    }
    ~this() {
        static if (is(typeof(getPayload.__dtor()))) {
            getPayload.__dtor();
        }
    }
}

final class Test { // 32 bytes each instance
    int i1, i2, i3, i4, i5, i6;
    this(int ii1, int ii2, int ii3, int ii4, int ii5, int ii6) {
        this.i1 = ii1;
        this.i2 = ii2;
        this.i3 = ii3;
        this.i4 = ii4;
        this.i5 = ii5;
        this.i6 = ii6;
    }
    void doSomething(int ii1, int ii2, int ii3, int ii4, int ii5, int ii6) {
    }
}

void main(string[] args)
{
    enum int N = 10_000_000;
    int i;
    while (i < N) {
        auto testObject = Scoped!Test(i, i, i, i, i, i);
        //scope testObject = new Test(i, i, i, i, i, i);
        testObject.doSomething(i, i, i, i, i, i);
        testObject.doSomething(i, i, i, i, i, i);
        testObject.doSomething(i, i, i, i, i, i);
        testObject.doSomething(i, i, i, i, i, i);
        i++;
    }
}


Andrei
July 21, 2010
Andrei Alexandrescu wrote:

> On 07/21/2010 01:59 PM, Rory Mcguire wrote:
>> Dmitry Olshansky wrote:
>>
>>> now replace the orignal while loop with this:
>>> while (i<  N) {
>>> auto testObject = Scoped!Test(i, i, i, i, i, i);
>>> //assuming we have aforementioned evil function func(Test t),
>>> that keeps global reference to t.
>>> //fun(testObject); //uncoment to get an compile error - type
>>> mismatch
>>> testObject.doSomething(i, i, i, i, i, i);
>>> testObject.doSomething(i, i, i, i, i, i);
>>> testObject.doSomething(i, i, i, i, i, i);
>>> testObject.doSomething(i, i, i, i, i, i);
>>> i++;
>>> }
>>
>> With your code I `time` reports the below timings on my machine:
>> real	0m19.658s
>> user	0m19.590s
>> sys	0m0.010s
>>
>> compared to:
>> real	0m9.122s
>> user	0m9.090s
>> sys	0m0.000s
>>
>> with bearofiles original version. With -O -release its about 4 seconds faster for each.
> 
> I compiled and ran the tests myself with -O -release -inline and got 1.95s for Dmitry's implementation and 1.55s for bearophile's.
> 
> I optimized Dmitry's implementation in two ways: I replaced the call to clear() with a straight call to the destructor and added = void in two places to avoid double initialization. I got 1.11s, which significantly undercuts the implementation using scope.
> 
> Here's the code I used for testing:
> 
> struct Scoped(T) {
>      ubyte[__traits(classInstanceSize, Test)] _payload = void;
>      T getPayload(){
>          return cast(T)(_payload.ptr);
>      }
>      alias getPayload this;
> 
>      static Scoped opCall(Args...)(Args args)
>      if (is(typeof(T.init.__ctor(args)))) {
>          // TODO: should also provide decent error message
>          Scoped!T s = void;
>          emplace!T(cast(void[])s._payload,args);
>          return s;
>      }
>      ~this() {
>          static if (is(typeof(getPayload.__dtor()))) {
>              getPayload.__dtor();
>          }
>      }
> }
> 
> final class Test { // 32 bytes each instance
>      int i1, i2, i3, i4, i5, i6;
>      this(int ii1, int ii2, int ii3, int ii4, int ii5, int ii6) {
>          this.i1 = ii1;
>          this.i2 = ii2;
>          this.i3 = ii3;
>          this.i4 = ii4;
>          this.i5 = ii5;
>          this.i6 = ii6;
>      }
>      void doSomething(int ii1, int ii2, int ii3, int ii4, int ii5, int
> ii6) {
>      }
> }
> 
> void main(string[] args)
> {
>      enum int N = 10_000_000;
>      int i;
>      while (i < N) {
>          auto testObject = Scoped!Test(i, i, i, i, i, i);
>          //scope testObject = new Test(i, i, i, i, i, i);
>          testObject.doSomething(i, i, i, i, i, i);
>          testObject.doSomething(i, i, i, i, i, i);
>          testObject.doSomething(i, i, i, i, i, i);
>          testObject.doSomething(i, i, i, i, i, i);
>          i++;
>      }
> }
> 
> 
> Andrei


Thanks Andrei!!!
July 21, 2010
Andrei Alexandrescu wrote:

> Here's the code I used for testing:
> 


My timings with your code, I included the one without -inline because I havn't been using -inline in my other tests:

$ dmd -O -release -inline program1_2.d
$ time ./program1_2

real	0m0.526s
user	0m0.520s
sys	0m0.000s
$ dmd -O -release program1_2.d
$ time ./program1_2

real	0m0.820s
user	0m0.810s
sys	0m0.000s

and bearofiles with -inline:
$ time ./program1

real	0m2.267s
user	0m2.260s
sys	0m0.000s



Nice improvement...so is this in phobos or druntime yet? :D
July 21, 2010
Rory Mcguire wrote:
> Andrei Alexandrescu wrote:
[snip]

> Thanks Andrei!!!

Don't mention it. Thanks for not over-quoting in the future :o).

Andrei
July 21, 2010
On 21.07.2010 23:16, Andrei Alexandrescu wrote:
> I compiled and ran the tests myself with -O -release -inline and got 1.95s for Dmitry's implementation and 1.55s for bearophile's.
>
> I optimized Dmitry's implementation in two ways: I replaced the call to clear() with a straight call to the destructor and added = void in two places to avoid double initialization. I got 1.11s, which significantly undercuts the implementation using scope.
>
> Here's the code I used for testing:
>
> struct Scoped(T) {
>     ubyte[__traits(classInstanceSize, Test)] _payload = void;
>     T getPayload(){
>         return cast(T)(_payload.ptr);
>     }
>     alias getPayload this;
>
>     static Scoped opCall(Args...)(Args args)
>     if (is(typeof(T.init.__ctor(args)))) {
>         // TODO: should also provide decent error message
>         Scoped!T s = void;
>         emplace!T(cast(void[])s._payload,args);
>         return s;
>     }
>     ~this() {
>         static if (is(typeof(getPayload.__dtor()))) {
>             getPayload.__dtor();
>         }
>     }
> }
Thanks for kind feedback (and showing some optimization tricks).  Also this implementation still has issues with it: it calls dtor twice. Not a good trait for RAII technique ! :)
Since it's now considered useful I feel myself obliged to enhance and correct it. Sadly enough I still haven't managed to apply an inner template trick.
Here's the end result along with a simple unittest:

struct Scoped(T){
    ubyte[__traits(classInstanceSize, T)] _scopedPayload = void;
    T getScopedPayload(){
        return cast(T)(_scopedPayload.ptr);
    }
    alias getScopedPayload this;
    this(Args...)(Args args){
        static if (!is(typeof(getScopedPayload.__ctor(args)))) {
            static assert(false,"Scoped: wrong arguments passed to ctor");
        }else {
            emplace!T(cast(void[])_scopedPayload,args);
        }
     }
    ~this() {
        static if (is(typeof(getScopedPayload.__dtor()))) {
            getScopedPayload.__dtor();
        }
    }
}
//also track down destruction/construction
class A{
    this(int a){ writeln("A with ",a); }
    this(real r){ writeln("A with ",r); }
    ~this(){ writeln("A destroyed");  }
}

unittest{
    {
        auto a = Scoped!A(42);
    }
    {
        auto b = Scoped!A(5.5);
    }
}

-- 
Dmitry Olshansky