Thread overview
Re: Passing Appender by value
Jun 22, 2013
Andrej Mitrovic
Jun 22, 2013
Andrej Mitrovic
Jun 22, 2013
Diggory
Jun 22, 2013
monarch_dodra
Jun 22, 2013
monarch_dodra
Jun 22, 2013
Andrej Mitrovic
Jun 22, 2013
Namespace
Jun 22, 2013
monarch_dodra
June 22, 2013
On 6/22/13, Andrej Mitrovic <andrej.mitrovich@gmail.com> wrote:
> I just learned today that passing Appender by value does not have the semantics I thought it would:
>
> -----
> import std.array;
> import std.stdio;
>
> void call(Appender!(int[]) buffer)
> {
>     buffer.put(1);
> }
>
> void main()
> {
>     Appender!(int[]) buffer;
>     writeln(buffer.data);  // writes [], it's empty
> }
> -----

WRONG example, there's a missing call there, I meant:

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

void call(Appender!(int[]) buffer)
{
    buffer.put(1);
}

void main()
{
    Appender!(int[]) buffer;
    call(buffer);
    writeln(buffer.data);  // writes [], it's empty
}
-----
June 22, 2013
On 6/22/13, Andrej Mitrovic <andrej.mitrovich@gmail.com> wrote:
>     Appender!(int[]) buffer;
>     call(buffer);
>     writeln(buffer.data);  // writes [], it's empty

Apparently it's the same thing as the old AA problem. Essentially the buffer would have to be initialized first by appending:

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

void call(Appender!(int[]) buffer)
{
    buffer.put(1);
}

void main()
{
    Appender!(int[]) buffer;
    call(buffer);
    assert(buffer.data.empty);  // passes

    call(buffer);
    assert(buffer.data.empty);  // still passes

    buffer.put(2);
    call(buffer);
    assert(buffer.data == [2, 1]);  // now it finally went through
}
-----

It has something to do with null-initialization. I remember the discussion about this problem with hashes, I just can't remember if there was a bug report about it to link to.
June 22, 2013
On Saturday, 22 June 2013 at 13:48:53 UTC, Andrej Mitrovic wrote:
> On 6/22/13, Andrej Mitrovic <andrej.mitrovich@gmail.com> wrote:
>>     Appender!(int[]) buffer;
>>     call(buffer);
>>     writeln(buffer.data);  // writes [], it's empty
>
> Apparently it's the same thing as the old AA problem. Essentially the
> buffer would have to be initialized first by appending:
>
> -----
> import std.array;
> import std.stdio;
>
> void call(Appender!(int[]) buffer)
> {
>     buffer.put(1);
> }
>
> void main()
> {
>     Appender!(int[]) buffer;
>     call(buffer);
>     assert(buffer.data.empty);  // passes
>
>     call(buffer);
>     assert(buffer.data.empty);  // still passes
>
>     buffer.put(2);
>     call(buffer);
>     assert(buffer.data == [2, 1]);  // now it finally went through
> }
> -----
>
> It has something to do with null-initialization. I remember the
> discussion about this problem with hashes, I just can't remember if
> there was a bug report about it to link to.

The problem occurs whenever the internal array is resized because what actually happens is that a new array is allocated and the contents copied over - the original Appender still references the original array (or in this case null). The last example only works because when you call "put(2)" it actually allocates an array large enough to hold at least another item as well.

As usual it can be solved by introducing an extra layer of indirection, either by passing by ref or by making Appender store a pointer to a range.
June 22, 2013
On Saturday, 22 June 2013 at 14:19:45 UTC, Diggory wrote:
> The problem occurs whenever the internal array is resized because what actually happens is that a new array is allocated and the contents copied over - the original Appender still references the original array (or in this case null). The last example only works because when you call "put(2)" it actually allocates an array large enough to hold at least another item as well.
>
> As usual it can be solved by introducing an extra layer of indirection, either by passing by ref or by making Appender store a pointer to a range.

No, it's not that bad, because the "extra layer of indirection" is already there. The problem is that said layer isn't actually initialized yet...
June 22, 2013
On Saturday, 22 June 2013 at 13:48:53 UTC, Andrej Mitrovic wrote:
> On 6/22/13, Andrej Mitrovic <andrej.mitrovich@gmail.com> wrote:
>>     Appender!(int[]) buffer;
>>     call(buffer);
>>     writeln(buffer.data);  // writes [], it's empty
>
> Apparently it's the same thing as the old AA problem. Essentially the
> buffer would have to be initialized first by appending:

Yeah... same old, same old.

I think you can work around the problem using "appender":
auto buffer = appender!(int[])();

This is off memory, so not 100% guaranteed, but I remember this worked for me last time *I* ran into this problem.

Indeed, this is a recurring problem in D with non-initialized reference types, AA's, Appender, Containers...

The fact there is no standardized way to make a call for run-time intialization with no arguments doesn't help either. I had made a request for having an explicit runtime constructor that takes no arguments:
http://forum.dlang.org/thread/bvuquzwfykiytdwsqkky@forum.dlang.org
Didn't really get anywhere though...

In any case, I think this is a ***HUGE*** problem for D (my N°1 problem anyways). static opCall() kind of helps, but it's not quite a constructor...

I think we're kind of doomed to keep this issue around unresolved, biting users in the ass *everyday*, for still quite some time :(
June 22, 2013
On 6/22/13, monarch_dodra <monarchdodra@gmail.com> wrote:
> I think you can work around the problem using "appender":
> auto buffer = appender!(int[])();

Yes, because it has a default argument which it passes to the Appender constructor. If only we had default constructors for structs in D.. or a general solution to this issue. I remember maybe a year ago or so I've ran into the same issue with hashes, and now it almost bit me hard again (luckily this was caught immediately in a failing unittest).
June 22, 2013
On Saturday, 22 June 2013 at 16:08:01 UTC, Andrej Mitrovic wrote:
> On 6/22/13, monarch_dodra <monarchdodra@gmail.com> wrote:
>> I think you can work around the problem using "appender":
>> auto buffer = appender!(int[])();
>
> Yes, because it has a default argument which it passes to the Appender
> constructor. If only we had default constructors for structs in D.. or
> a general solution to this issue. I remember maybe a year ago or so
> I've ran into the same issue with hashes, and now it almost bit me
> hard again (luckily this was caught immediately in a failing
> unittest).

What was the main reason for Walter to disable default CTors for structs?
June 22, 2013
On Saturday, 22 June 2013 at 16:31:16 UTC, Namespace wrote:
> On Saturday, 22 June 2013 at 16:08:01 UTC, Andrej Mitrovic wrote:
>> On 6/22/13, monarch_dodra <monarchdodra@gmail.com> wrote:
>>> I think you can work around the problem using "appender":
>>> auto buffer = appender!(int[])();
>>
>> Yes, because it has a default argument which it passes to the Appender
>> constructor. If only we had default constructors for structs in D.. or
>> a general solution to this issue. I remember maybe a year ago or so
>> I've ran into the same issue with hashes, and now it almost bit me
>> hard again (luckily this was caught immediately in a failing
>> unittest).
>
> What was the main reason for Walter to disable default CTors for structs?

So that *default* construction can be evaluated statically, which then means .init can exist, which allows all of D's postblit mechanics, as well as move semantics.

It also means that declaring statics in a function is possible and doesn't require any special run-time code for first initialization.

I think the problem is that since in C++ "no-arg => default", they removed constructors that took no arguments, so as to avoid any confusion, or maybe they just didn't realize that someone might want to runtime construct something, but have no arguments to pass to the constructor.

IMO, there was a *little* bit of lack of hindsight on that one, but the choice was clearly superior to C++'s CC scheme on return by value...