Jump to page: 1 2 3
Thread overview
Scope of temporaries as function arguments
Jun 28, 2013
Nick Sabalausky
Jun 28, 2013
Maxim Fomin
Jun 28, 2013
Maxim Fomin
Jun 28, 2013
Nick Sabalausky
Jun 28, 2013
monarch_dodra
Jun 28, 2013
Maxim Fomin
Jun 28, 2013
monarch_dodra
Jun 28, 2013
monarch_dodra
Jun 28, 2013
Maxim Fomin
Jun 28, 2013
monarch_dodra
Jun 28, 2013
Ali Çehreli
Jun 28, 2013
Ali Çehreli
Jun 28, 2013
Maxim Fomin
Jun 28, 2013
Ali Çehreli
Jun 28, 2013
monarch_dodra
Jun 28, 2013
Ali Çehreli
Jun 28, 2013
Maxim Fomin
Jun 28, 2013
monarch_dodra
Jun 28, 2013
Maxim Fomin
Jun 28, 2013
Marco Leise
June 28, 2013
Probably a silly question, but I wanted to double-check...

If you have this:

    struct Foo {...}
    bar(Foo());

Then regardless of optimizations (aside from any optimizer bugs, of course) the Foo temporary can't go out of scope or have its dtor called until bar finishes executing, right?

Or I guess more accurately, is there any guarantee that the assert in func() below should always pass?:

    class Foo {
        int i = 1;
        //...etc...
    }

    struct Bar {
        Foo foo;
        ~this() {
            foo.i = 2;
        }
        //...etc...
    }

    void func(Bar bar)
    {
        //...anything here that *doesn't* change bar.foo.i...

        assert(bar.foo.i == 1);  // Guaranteed to pass?
    }

    void main() {
        Foo f = new Foo();
        func(Bar(f));
    }

June 28, 2013
On Friday, 28 June 2013 at 04:54:56 UTC, Nick Sabalausky wrote:
> Probably a silly question, but I wanted to double-check...
>
> If you have this:
>
>     struct Foo {...}
>     bar(Foo());
>
> Then regardless of optimizations (aside from any optimizer bugs, of
> course) the Foo temporary can't go out of scope or have its dtor called
> until bar finishes executing, right?

Struct dtor is always called in the end of the caller (bar in example).

This will be OK, but in general case no. Currently object is copied in caller side but destroyed in callee side, and if one of the arguments next to struct is passed by invoking function which throws, callee and respectively dtor will never be called.

> Or I guess more accurately, is there any guarantee that the assert in
> func() below should always pass?:
>
>     class Foo {
>         int i = 1;
>         //...etc...
>     }
>
>     struct Bar {
>         Foo foo;
>         ~this() {
>             foo.i = 2;
>         }
>         //...etc...
>     }
>
>     void func(Bar bar)
>     {
>         //...anything here that *doesn't* change bar.foo.i...
>
>         assert(bar.foo.i == 1);  // Guaranteed to pass?
>     }
>
>     void main() {
>         Foo f = new Foo();
>         func(Bar(f));
>     }

Here yes, but in general case if there are other arguments, and one if them passed by lambda invocation which touches f and modifies, then no.
June 28, 2013
On Friday, 28 June 2013 at 05:11:11 UTC, Maxim Fomin wrote:
>
> Struct dtor is always called in the end of the caller (bar in example).
>

Callee of course.
June 28, 2013
On Fri, 28 Jun 2013 07:11:05 +0200
"Maxim Fomin" <maxim@maxim-fomin.ru> wrote:

> On Friday, 28 June 2013 at 04:54:56 UTC, Nick Sabalausky wrote:
> > Probably a silly question, but I wanted to double-check...
> >
> > If you have this:
> >
> >     struct Foo {...}
> >     bar(Foo());
> >
> > Then regardless of optimizations (aside from any optimizer
> > bugs, of
> > course) the Foo temporary can't go out of scope or have its
> > dtor called
> > until bar finishes executing, right?
> 
> Struct dtor is always called in the end of the caller (bar in example).
> 
> This will be OK, but in general case no. Currently object is copied in caller side but destroyed in callee side, and if one of the arguments next to struct is passed by invoking function which throws, callee and respectively dtor will never be called.
> 

Interesting.

BTW, for anyone else reading, I just searched bugzilla and it looks like the relevant issue is #9704.

Kinda ugly as it means refcounted RAII structs can leek under this condition:

func(refCountedStruct, thisThrows());

Ouch.

> > Or I guess more accurately, is there any guarantee that the
> > assert in
> > func() below should always pass?:
> >
> >     class Foo {
> >         int i = 1;
> >         //...etc...
> >     }
> >
> >     struct Bar {
> >         Foo foo;
> >         ~this() {
> >             foo.i = 2;
> >         }
> >         //...etc...
> >     }
> >
> >     void func(Bar bar)
> >     {
> >         //...anything here that *doesn't* change bar.foo.i...
> >
> >         assert(bar.foo.i == 1);  // Guaranteed to pass?
> >     }
> >
> >     void main() {
> >         Foo f = new Foo();
> >         func(Bar(f));
> >     }
> 
> Here yes, but in general case if there are other arguments, and one if them passed by lambda invocation which touches f and modifies, then no.

Cool, thanks.

June 28, 2013
On Friday, 28 June 2013 at 06:15:26 UTC, Nick Sabalausky wrote:
> On Fri, 28 Jun 2013 07:11:05 +0200
> "Maxim Fomin" <maxim@maxim-fomin.ru> wrote:
>
>> On Friday, 28 June 2013 at 04:54:56 UTC, Nick Sabalausky wrote:
>> > Probably a silly question, but I wanted to double-check...
>> >
>> > If you have this:
>> >
>> >     struct Foo {...}
>> >     bar(Foo());
>> >
>> > Then regardless of optimizations (aside from any optimizer bugs, of
>> > course) the Foo temporary can't go out of scope or have its dtor called
>> > until bar finishes executing, right?
>> 
>> Struct dtor is always called in the end of the caller (bar in example).
>> 
>> This will be OK, but in general case no. Currently object is copied in caller side but destroyed in callee side, and if one of the arguments next to struct is passed by invoking function which throws, callee and respectively dtor will never be called.
>> 
>
> Interesting.
>
> BTW, for anyone else reading, I just searched bugzilla and it looks
> like the relevant issue is #9704.
>
> Kinda ugly as it means refcounted RAII structs can leek under this
> condition:
>
> func(refCountedStruct, thisThrows());
>
> Ouch.

Just in case it wasn't clear from the original explanation, this is a bug, it *should* be perfectly safe to pass as many temps as you want, and expect the right amount of destructor called in case of a throw.
June 28, 2013
Am Fri, 28 Jun 2013 02:15:19 -0400
schrieb Nick Sabalausky <SeeWebsiteToContactMe@semitwist.com>:

> BTW, for anyone else reading, I just searched bugzilla and it looks like the relevant issue is #9704.

Reminds me of an issue I reported later: http://d.puremagic.com/issues/show_bug.cgi?id=10409

-- 
Marco

June 28, 2013
On Friday, 28 June 2013 at 08:08:17 UTC, monarch_dodra wrote:
>
> Just in case it wasn't clear from the original explanation, this is a bug, it *should* be perfectly safe to pass as many temps as you want, and expect the right amount of destructor called in case of a throw.

Original explanation lacks the word "bug" deliberately because this is not a bug (in a sense that dmd generates wrong code), but a language design problem. How could you do this:

struct S
{
   int i = 1;
}

void foo(S s)
{
   s.i = 2;
}

void main()
{
   S s;
   foo(s);
}

Currently there are two dtors, one which gets S(2) at the end of foo and second at the end of main, which gets S(1). If you move dtor from callee to caller, it would get S(1) object (struct is passed by value), but it doesn't make sense to destruct S(1) where you have S(2). One possible solution is to pass by pointer in low level, which would probably increase magnitude of problems.
June 28, 2013
On Friday, 28 June 2013 at 14:26:04 UTC, Maxim Fomin wrote:
> On Friday, 28 June 2013 at 08:08:17 UTC, monarch_dodra wrote:
>>
>> Just in case it wasn't clear from the original explanation, this is a bug, it *should* be perfectly safe to pass as many temps as you want, and expect the right amount of destructor called in case of a throw.
>
> Original explanation lacks the word "bug" deliberately because this is not a bug (in a sense that dmd generates wrong code), but a language design problem. How could you do this:
>
> struct S
> {
>    int i = 1;
> }
>
> void foo(S s)
> {
>    s.i = 2;
> }
>
> void main()
> {
>    S s;
>    foo(s);
> }
>
> Currently there are two dtors, one which gets S(2) at the end of foo and second at the end of main, which gets S(1). If you move dtor from callee to caller, it would get S(1) object (struct is passed by value), but it doesn't make sense to destruct S(1) where you have S(2). One possible solution is to pass by pointer in low level, which would probably increase magnitude of problems.

I don't understand the problem... There *should* be two destroyers... "main.s" is postblitted into "foo.s", and then foo destroys "foo.s" at the end of its scope...

Where is the problem here?
June 28, 2013
On Friday, 28 June 2013 at 15:12:01 UTC, monarch_dodra wrote:
> On Friday, 28 June 2013 at 14:26:04 UTC, Maxim Fomin wrote:
>> [...]
>
> I don't understand the problem... There *should* be two destroyers... "main.s" is postblitted into "foo.s", and then foo destroys "foo.s" at the end of its scope...
>
> Where is the problem here?

--------
import std.stdio;

struct S
{
   int i = 0;
    this(int i){this.i = i; writeln("constructing: ", i);}
    this(this){writeln("postbliting: ", i);}
    ~this(){writeln("destroying: ", i);}
}

void foo(S s)
{
   s.i = 2;
}

void main()
{
   S s = S(1);
   foo(s);
}
--------
constructing: 1
postbliting: 1
destroying: 2
destroying: 1
--------

Should I have expected a different behavior?
June 28, 2013
On Friday, 28 June 2013 at 15:17:12 UTC, monarch_dodra wrote:
>
> Should I have expected a different behavior?

import std.stdio;

int callme()
{
   throw new Exception("");
}

struct S
{
   int i = 0;
    this(int i){this.i = i; writeln("constructing: ", i);}
    this(this){writeln("postbliting: ", i);}
    ~this(){writeln("destroying: ", i);}
}

void foo(S s, int i)
{
   s.i = 2;
}

void main()
{
   S s = S(1);
   foo(s, callme());
}

Destructor for copied object is not called because it is placed in foo(). Before calling foo(), dmd makes a copy of main.s, calls postblit, then puts code to invoke callme() and code to invoke foo(). Since callme() throws, foo() is not called and destructor placed in foo() is also not called. A struct copy escapes destructor.

Now, if you try fix this by putting dtor for copy not in foo(), but in main immediately after foo() invocation, you will have a problem because destructor would get S(1) object while it should destroy S(2). Any modification made in foo() is lost. This can be possible fixed by passing copy by reference which would probably create new ABI problems.
« First   ‹ Prev
1 2 3