www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Cannot use std.array.Appender in recursive types

reply =?UTF-8?B?Tm9yZGzDtnc=?= <per.nordlow gmail.com> writes:
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:
Aug 09
parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
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
Aug 09
next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
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); }
Aug 09
prev sibling parent reply =?UTF-8?B?Tm9yZGzDtnc=?= <per.nordlow gmail.com> writes:
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?
Aug 09
parent Steven Schveighoffer <schveiguy yahoo.com> writes:
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
Aug 09