Thread overview
Cannot use std.array.Appender in recursive types
Aug 09, 2017
Nordlöw
Aug 09, 2017
Timon Gehr
Aug 09, 2017
Nordlöw
August 09, 2017
Why doesn't appending to `subs` work with std.array.Appender in

    struct T
    {
        string src;
        import std.array : Appender;
        Appender!(T[]) subs;
    }
    T t;
    t.subs ~= T.init; // ERRORS
    t.subs.put(T.init); // ERRORS

when it works with builtin arrays as in

    struct S
    {
        string src;
        S[] subs;
    }
    S s;
    s.subs ~= S.init;

?

Specifically

    t.subs ~= T.init

errors as

    Error: cannot append type T to type Appender!(T[])

and

    t.subs.put(T.init);

errors as

    Error: template std.array.Appender!(T[]).Appender.put cannot deduce function from argument types !()(T), candidates are:

August 09, 2017
On 8/9/17 2:25 PM, Nordlöw wrote:
> Why doesn't appending to `subs` work with std.array.Appender in
> 
>      struct T
>      {
>          string src;
>          import std.array : Appender;
>          Appender!(T[]) subs;
>      }
>      T t;
>      t.subs ~= T.init; // ERRORS
>      t.subs.put(T.init); // ERRORS
> 
> when it works with builtin arrays as in
> 
>      struct S
>      {
>          string src;
>          S[] subs;
>      }
>      S s;
>      s.subs ~= S.init;
> 
> ?
> 
> Specifically
> 
>      t.subs ~= T.init
> 
> errors as
> 
>      Error: cannot append type T to type Appender!(T[])

Here is the problem:

ElementType!(T[]) is void.

Here is ElementType:
template ElementType(R)
{
    static if (is(typeof(R.init.front.init) T))
        alias ElementType = T;
    else
        alias ElementType = void;
}

So what is happening here (I think), is that T isn't fully defined, so T.init.front.init is an error at this point. Therefore the else clause is selected.

This is one of the problems with using is(typeof) (or __traits(compiles)) with an "else" clause. You may not be checking what you think you are checking, and end up with the wrong result.

So essentially, Appender!(T[]) inside a T becomes (essentially) Appender!(void[]). And you can't append a T to a void[].

If I change the definition of ElementType to use R.init.front instead of R.init.front.init, it compiles. But I'm pretty sure this will break other ranges.

Somewhere, there's an answer.

-Steve
August 09, 2017
On 09.08.2017 21:00, Steven Schveighoffer wrote:
> On 8/9/17 2:25 PM, Nordlöw wrote:
>> Why doesn't appending to `subs` work with std.array.Appender in
>>
>>      struct T
>>      {
>>          string src;
>>          import std.array : Appender;
>>          Appender!(T[]) subs;
>>      }
>>      T t;
>>      t.subs ~= T.init; // ERRORS
>>      t.subs.put(T.init); // ERRORS
>>
>> when it works with builtin arrays as in
>>
>>      struct S
>>      {
>>          string src;
>>          S[] subs;
>>      }
>>      S s;
>>      s.subs ~= S.init;
>>
>> ?
>>
>> Specifically
>>
>>      t.subs ~= T.init
>>
>> errors as
>>
>>      Error: cannot append type T to type Appender!(T[])
> 
> Here is the problem:
> 
> ElementType!(T[]) is void.
> 
> Here is ElementType:
> template ElementType(R)
> {
>      static if (is(typeof(R.init.front.init) T))
>          alias ElementType = T;
>      else
>          alias ElementType = void;
> }
> 
> So what is happening here (I think), is that T isn't fully defined, so T.init.front.init is an error at this point. Therefore the else clause is selected.
> 
> This is one of the problems with using is(typeof) (or __traits(compiles)) with an "else" clause. You may not be checking what you think you are checking, and end up with the wrong result.
> 
> So essentially, Appender!(T[]) inside a T becomes (essentially) Appender!(void[]). And you can't append a T to a void[].
> 
> If I change the definition of ElementType to use R.init.front instead of R.init.front.init, it compiles. But I'm pretty sure this will break other ranges.
> 
> Somewhere, there's an answer.
> 
> -Steve

It's a forward reference bug. Self-contained test case:

auto front(T)(T[] a){ return a[0]; }

template ElementType(R){
    pragma(msg, typeof(R.init.front)); /* Error: struct bug.T no size because of forward reference */
    static if (is(typeof(R.init.front) T)) alias ElementType = T;
    else alias ElementType = void;
}
struct Appender(A){
    A a;
    alias T=ElementType!A;
    private enum canPutItem(U)=is(U==T);
    void put(T)(T t)if(canPutItem!T){
        a~=t;
    }
}
struct T{
    string src;
    Appender!(T[]) subs;
}
void main(){
    T t;
    t.subs.put(T.init);
}

August 09, 2017
On Wednesday, 9 August 2017 at 19:00:54 UTC, Steven Schveighoffer wrote:
> If I change the definition of ElementType to use R.init.front instead of R.init.front.init, it compiles. But I'm pretty sure this will break other ranges.

If Phobos compiles with the change would that change deserve a PR?
August 09, 2017
On 8/9/17 6:24 PM, Nordlöw wrote:
> On Wednesday, 9 August 2017 at 19:00:54 UTC, Steven Schveighoffer wrote:
>> If I change the definition of ElementType to use R.init.front instead of R.init.front.init, it compiles. But I'm pretty sure this will break other ranges.
> 
> If Phobos compiles with the change would that change deserve a PR?

I'm pretty sure it will fail in cases where front is a non-@property function.

I think in terms of Timon's post, he is doing what I did, and it's not working. I think it's a forward reference bug, and that just needs to be fixed.

-Steve