www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - How to expand an expression along with a parameter tuple?

reply "TommiT" <tommitissari hotmail.com> writes:
I can't figure out how to do the following C++ code in D:

int arr[] = { 1, 3, 5, 7, 11 };

template <typename... T>
void foo(T... values) { }

template <typename... T>
void bar(T... values)
{
     foo((arr[values] * 10)...);
}

int main()
{
     bar(1, 3, 4); /* calls foo(arr[1] * 10,
                                arr[3] * 10,
                                arr[4] * 10); */
     return 0;
}


This is how I tried to do it in D, but it doesn't work:

import std.conv;
import std.typetuple;

int[5] arr = [ 1, 3, 5, 7, 11 ];

void foo(T...)(T values) { }

void bar(T...)(T values)
{
     foo(expandWrapped!("arr[ ] * 10", values));
}

template expandWrapped(string fmt, X...)
{
     string compose()
     {
         string ret = "alias expandWrapped = TypeTuple!(";

         foreach (i; 0 .. X.length)
         {
             auto iStr = to!string(i);

             foreach (ch; fmt)
             {
                 if (ch == ' ')
                 {
                     ret ~= "X[" ~ iStr ~ "]";
                 }
                 else
                 {
                     ret ~= ch;
                 }
             }

             if (i != X.length - 1)
             {
                 ret ~= ",";
             }
         }
         ret ~= ");";
         return ret;
     }
     mixin(compose()); // [1]
}

void main()
{
     bar(1, 3, 4);
}

1) Error: variable arr cannot be read at compile time
Jun 16 2013
next sibling parent "TommiT" <tommitissari hotmail.com> writes:
Although... now that I think about it, this should really be done 
as a language feature, and not through some inefficient CTFE 
trick. So, I should really be able to just write this:

int[5] arr = [ 1, 3, 5, 7, 11 ];

void foo(T...)(T values) { }

void bar(T...)(T values)
{
     foo((arr[values] * 10)...);
}

void main()
{
     bar(1, 3, 4);
}
Jun 16 2013
prev sibling parent reply =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
On 06/16/2013 11:19 PM, TommiT wrote:

 I can't figure out how to do the following C++ code in D:

 int arr[] = { 1, 3, 5, 7, 11 };

 template <typename... T>
 void foo(T... values) { }

 template <typename... T>
 void bar(T... values)
 {
      foo((arr[values] * 10)...);
 }

 int main()
 {
      bar(1, 3, 4); /* calls foo(arr[1] * 10,
                                 arr[3] * 10,
                                 arr[4] * 10); */
      return 0;
 }
The following does not answer the question of expanding but at least foo() receives [30, 70, 110] :) import std.stdio; import std.algorithm; import std.array; import std.range; int[] arr = [ 1, 3, 5, 7, 11 ]; void foo(T)(T[] values...) { writeln(values); } void bar(T)(T[] values...) { foo(arr .indexed(values) .map!(a => a * 10) .array); } void main() { bar(1, 3, 4); } Ali
Jun 17 2013
parent reply "TommiT" <tommitissari hotmail.com> writes:
On Monday, 17 June 2013 at 07:20:23 UTC, Ali Çehreli wrote:
 The following does not answer the question of expanding but at 
 least foo() receives [30, 70, 110] :)

 import std.stdio;
 import std.algorithm;
 import std.array;
 import std.range;

 int[] arr = [ 1, 3, 5, 7, 11 ];

 void foo(T)(T[] values...)
 {
     writeln(values);
 }

 void bar(T)(T[] values...)
 {
     foo(arr
         .indexed(values)
         .map!(a => a * 10)
         .array);
 }

 void main()
 {
     bar(1, 3, 4);
 }

 Ali
Yeah, that would work. I'd hate the overhead though.
Jun 17 2013
next sibling parent reply Artur Skawina <art.08.09 gmail.com> writes:
On 06/17/13 11:32, TommiT wrote:
 On Monday, 17 June 2013 at 07:20:23 UTC, Ali Çehreli wrote:
 The following does not answer the question of expanding but at least foo()
receives [30, 70, 110] :)

 import std.stdio;
 import std.algorithm;
 import std.array;
 import std.range;

 int[] arr = [ 1, 3, 5, 7, 11 ];

 void foo(T)(T[] values...)
 {
     writeln(values);
 }

 void bar(T)(T[] values...)
 {
     foo(arr
         .indexed(values)
         .map!(a => a * 10)
         .array);
 }

 void main()
 {
     bar(1, 3, 4);
 }

 Ali
Yeah, that would work. I'd hate the overhead though.
void bar(T...)(T values) { T tmp; foreach (i, ref v; values) tmp[i] = arr[v]*10; foo(tmp); } artur
Jun 17 2013
parent reply "TommiT" <tommitissari hotmail.com> writes:
On Monday, 17 June 2013 at 11:15:24 UTC, Artur Skawina wrote:
 On 06/17/13 11:32, TommiT wrote:
 On Monday, 17 June 2013 at 07:20:23 UTC, Ali Çehreli wrote:
 The following does not answer the question of expanding but 
 at least foo() receives [30, 70, 110] :)

 import std.stdio;
 import std.algorithm;
 import std.array;
 import std.range;

 int[] arr = [ 1, 3, 5, 7, 11 ];

 void foo(T)(T[] values...)
 {
     writeln(values);
 }

 void bar(T)(T[] values...)
 {
     foo(arr
         .indexed(values)
         .map!(a => a * 10)
         .array);
 }

 void main()
 {
     bar(1, 3, 4);
 }

 Ali
Yeah, that would work. I'd hate the overhead though.
void bar(T...)(T values) { T tmp; foreach (i, ref v; values) tmp[i] = arr[v]*10; foo(tmp); } artur
Cool, I didn't know that you could create multiple variables like that (T tmp).
Jun 17 2013
parent reply Artur Skawina <art.08.09 gmail.com> writes:
On 06/17/13 13:23, TommiT wrote:
 On Monday, 17 June 2013 at 11:15:24 UTC, Artur Skawina wrote:
    void bar(T...)(T values) {
       T tmp;
       foreach (i, ref v; values)
          tmp[i] = arr[v]*10;
       foo(tmp);
    }
Cool, I didn't know that you could create multiple variables like that (T tmp).
A more correct, but a bit less readable version (the types of 'values' and 'arr' elements do not have to match) would be: void bar(T...)(T values) { static if (T.length) { NTup!(T.length, typeof(arr[T[0].init])) tmp; foreach (i, ref v; values) tmp[i] = arr[v]*10; foo(tmp); } else foo(); } template NTup(size_t N, T...) { static if (N>1) alias NTup = NTup!(N-1, T, T[$-1]); else alias NTup = T; } artur
Jun 17 2013
parent reply "TommiT" <tommitissari hotmail.com> writes:
On Monday, 17 June 2013 at 12:21:31 UTC, Artur Skawina wrote:
 A more correct, but a bit less readable version (the types of 
 'values' and 'arr'
 elements do not have to match) would be:

    void bar(T...)(T values) {
       static if (T.length) {
          NTup!(T.length, typeof(arr[T[0].init])) tmp;
          foreach (i, ref v; values)
             tmp[i] = arr[v]*10;
          foo(tmp);
       }
       else
          foo();
    }
 
    template NTup(size_t N, T...) {
       static if (N>1)
          alias NTup = NTup!(N-1, T, T[$-1]);
       else
          alias NTup = T;
    }

 artur
Argh, that's a lot of boilerplate. Thanks for pointing this out. I didn't notice in your previous example that the expression types had to match with the parameter types. Now I really do think that we need the C++ ellipsis notation (it's just the ellipsis can be omitted when it would be right next to the tuple): void bar(T...)(T values) { foo((arr[values] * 10)...); }
Jun 17 2013
parent reply Artur Skawina <art.08.09 gmail.com> writes:
On 06/17/13 14:57, TommiT wrote:
 On Monday, 17 June 2013 at 12:21:31 UTC, Artur Skawina wrote:
 A more correct, but a bit less readable version (the types of 'values' and
'arr'
 elements do not have to match) would be:

    void bar(T...)(T values) {
       static if (T.length) {
          NTup!(T.length, typeof(arr[T[0].init])) tmp;
          foreach (i, ref v; values)
             tmp[i] = arr[v]*10;
          foo(tmp);
       }
       else
          foo();
    }

    template NTup(size_t N, T...) {
       static if (N>1)
          alias NTup = NTup!(N-1, T, T[$-1]);
       else
          alias NTup = T;
    }
Argh, that's a lot of boilerplate. Thanks for pointing this out. I didn't notice in your previous example that the expression types had to match with the parameter types. Now I really do think that we need the C++ ellipsis notation (it's just the ellipsis can be omitted when it would be right next to the tuple): void bar(T...)(T values) { foo((arr[values] * 10)...); }
Well, the only real difference between these two examples is s/T tmp/NTup!(T.length, typeof(arr[T[0].init])) tmp/ 'NTup' would be a lib thing, and the empty-args case would have to handled (or disallowed) in real code anyway. So it's not /that/ much more boilerplate. Another solution would be to have the following hidden in some lib: struct _ForEach(alias MAP, TS...) { NTup!(TS.length, typeof(MAP(TS[0].init))) tuple; this(TS values) { foreach (i, ref v; values) tuple[i] = MAP(v); } } auto ForEach(alias MAP, TS...)(TS ts) { return _ForEach!(MAP, TS)(ts); } Then 'bar' becomes just: void bar(T...)(T values) { foo(ForEach!(a=>arr[a]*10)(values).tuple); } Yes, this is not as concise as '...' would be. But, with a bit more tuple support in the language, the '.tuple' part wouldn't be necessary, and then it's just foo(ForEach!(a=>arr[a]*10)(values)); vs foo((arr[values] * 10)...); artur
Jun 17 2013
next sibling parent reply "bearophile" <bearophileHUGS lycos.com> writes:
Artur Skawina:

 Yes, this is not as concise as '...' would be. But, with a bit 
 more tuple support in the language, the '.tuple' part wouldn't 
 be
 necessary,
Implicit things are dangerous in languages. ".tuple" can also be written "[]". Bye, bearophile
Jun 17 2013
parent reply Artur Skawina <art.08.09 gmail.com> writes:
On 06/17/13 16:20, bearophile wrote:
 Artur Skawina:
 
 Yes, this is not as concise as '...' would be. But, with a bit more tuple
support in the language, the '.tuple' part wouldn't be
 necessary,
Implicit things are dangerous in languages.
Not sure what you mean. "A bit more tuple support" means aot being able to return /real/ tuples from functions. This just needs to be specced; can be handled like I did in the ForEach example - by wrapping it in a struct, only internally. Then this becomes possible: auto ForEach(alias MAP, TS...)(TS ts) { NTup!(TS.length, typeof(MAP(TS[0].init))) tuple; foreach (i, ref v; values) tuple[i] = MAP(v); return tuple; } void bar(T...)(T values) { foo(ForEach!(a=>arr[a]*10)(values)); } which is already much more readable. And 100% explicit. While /it's only syntax sugar/, it does remove a lot of noise.
 ".tuple" can also be written "[]".
No idea what you mean by this. artur
Jun 17 2013
parent reply "bearophile" <bearophileHUGS lycos.com> writes:
Artur Skawina:

 ".tuple" can also be written "[]".
No idea what you mean by this.
If in the code you wrote you replace the first ".tuple" with "[]" the code keeps working. Bye, bearophile
Jun 17 2013
parent reply Artur Skawina <art.08.09 gmail.com> writes:
On 06/17/13 23:11, bearophile wrote:
 Artur Skawina:
 
 ".tuple" can also be written "[]".
No idea what you mean by this.
If in the code you wrote you replace the first ".tuple" with "[]" the code keeps working.
It does not - I really have no idea what you mean; slicing a struct does not (and can not) produce an auto-expanding tuple, unless I'm missing some recent language change... artur
Jun 17 2013
parent "bearophile" <bearophileHUGS lycos.com> writes:
Artur Skawina:

 slicing a struct
 does not (and can not) produce an auto-expanding tuple, unless 
 I'm missing
 some recent language change...
Sorry, I have misunderstood the type, the [] works on typetuples. Bye, bearophile
Jun 18 2013
prev sibling next sibling parent reply "TommiT" <tommitissari hotmail.com> writes:
On Monday, 17 June 2013 at 13:59:34 UTC, Artur Skawina wrote:
 Another solution would be to have the following hidden in some 
 lib:

    struct _ForEach(alias MAP, TS...) {
       NTup!(TS.length, typeof(MAP(TS[0].init))) tuple;

       this(TS values) {
          foreach (i, ref v; values)
             tuple[i] = MAP(v);
       }
    }

    auto ForEach(alias MAP, TS...)(TS ts) {
       return _ForEach!(MAP, TS)(ts);
    }
 
 Then 'bar' becomes just:

    void bar(T...)(T values) {
       foo(ForEach!(a=>arr[a]*10)(values).tuple);
    }

 Yes, this is not as concise as '...' would be. But, with a bit 
 more
 tuple support in the language, the '.tuple' part wouldn't be
 necessary, and then it's just

       foo(ForEach!(a=>arr[a]*10)(values));
 vs
       foo((arr[values] * 10)...);

 artur
Now, this is pretty cool. But I wonder a couple of things: 1) What kind of an impact does this have on compilation times compared to having this new ellipsis syntax which would allow the compiler to do a simple rewrite. 2) I wonder if the compiler can optimize that _ForEach struct away.
Jun 17 2013
parent reply Artur Skawina <art.08.09 gmail.com> writes:
On 06/18/13 03:51, TommiT wrote:
 On Monday, 17 June 2013 at 13:59:34 UTC, Artur Skawina wrote:
    struct _ForEach(alias MAP, TS...) {
       NTup!(TS.length, typeof(MAP(TS[0].init))) tuple;

       this(TS values) {
          foreach (i, ref v; values)
             tuple[i] = MAP(v);
       }
    }

    auto ForEach(alias MAP, TS...)(TS ts) {
       return _ForEach!(MAP, TS)(ts);
    }

    void bar(T...)(T values) {
       foo(ForEach!(a=>arr[a]*10)(values).tuple);
    }
 Now, this is pretty cool. But I wonder a couple of things:
 1) What kind of an impact does this have on compilation times compared to
having this new ellipsis syntax which would allow the compiler to do a simple
rewrite.
Not a significant one, I'd expect; for simple cases like these it shouldn't make much difference.
 2) I wonder if the compiler can optimize that _ForEach struct away.
Yes.
 Change the call to bar(1, 3L); and it wouldn't even compile.
 It's because all the types of _ForEach.tuple are the same as the first element
of TS...
 
 I mean... the same as the type of MAP(TS[0])
That was enough to handle the original problem, iirc. Making the code work for heterogeneous args (and mapping functions) is trivial, though: import std.stdio; template _TypeMap(alias MAP, size_t N, TS...) { static if (N<TS.length) alias _TypeMap = _TypeMap!(MAP, N+1, TS[0..N], typeof(MAP(TS[N].init), TS[N..$])); else alias _TypeMap = TS; } template TypeMap(alias MAP, TS...) { alias TypeMap = _TypeMap!(MAP, 0, TS); } struct _ForEach(alias MAP, TS...) { TypeMap!(MAP, TS) tuple; this(TS values) { foreach (i, ref v; values) tuple[i] = MAP(v); } } auto ForEach(alias MAP, TS...)(TS ts) { return _ForEach!(MAP, TS)(ts); } void foo(T...)(T values) { foreach (v; values) writeln(v); } void bar(T...)(T values) { foo(ForEach!(a => a + 1)(values).tuple); } void main() { bar(10, 3_000_000_000u, 2.14, -43L); } artur
Jun 18 2013
next sibling parent "TommiT" <tommitissari hotmail.com> writes:
On Tuesday, 18 June 2013 at 10:57:00 UTC, Artur Skawina wrote:
 On 06/18/13 03:51, TommiT wrote:
 Change the call to bar(1, 3L); and it wouldn't even compile.
 It's because all the types of _ForEach.tuple are the same as 
 the first element of TS...
 
 I mean... the same as the type of MAP(TS[0])
That was enough to handle the original problem, iirc. Making the code work for heterogeneous args (and mapping functions) is trivial, though: import std.stdio; template _TypeMap(alias MAP, size_t N, TS...) { static if (N<TS.length) alias _TypeMap = _TypeMap!(MAP, N+1, TS[0..N], typeof(MAP(TS[N].init), TS[N..$])); else alias _TypeMap = TS; } template TypeMap(alias MAP, TS...) { alias TypeMap = _TypeMap!(MAP, 0, TS); } struct _ForEach(alias MAP, TS...) { TypeMap!(MAP, TS) tuple; this(TS values) { foreach (i, ref v; values) tuple[i] = MAP(v); } } auto ForEach(alias MAP, TS...)(TS ts) { return _ForEach!(MAP, TS)(ts); } void foo(T...)(T values) { foreach (v; values) writeln(v); } void bar(T...)(T values) { foo(ForEach!(a => a + 1)(values).tuple); } void main() { bar(10, 3_000_000_000u, 2.14, -43L); } artur
Okay, you really seem to have figured this stuff out. I think I have difficulty seeing the solution because I'm used to C++ parameter packs, which have much less functionality than parameter tuples in D + there's no static if. But I learned a lot from this, thanks for that.
Jun 18 2013
prev sibling parent "TommiT" <tommitissari hotmail.com> writes:
On Tuesday, 18 June 2013 at 10:57:00 UTC, Artur Skawina wrote:
    template _TypeMap(alias MAP, size_t N, TS...) {
        static if (N<TS.length)
            alias _TypeMap = _TypeMap!(MAP, N+1, TS[0..N], 
 typeof(MAP(TS[N].init), TS[N..$]));
        else
            alias _TypeMap = TS;
    }
There were some typos. Here's the correct one: template _TypeMap(alias MAP, size_t N, TS...) { static if (N<TS.length) alias _TypeMap = _TypeMap!(MAP, N+1, TS[0..N], typeof(MAP(TS[N].init)), TS[N+1..$]); else alias _TypeMap = TS; }
Jul 06 2013
prev sibling parent reply "TommiT" <tommitissari hotmail.com> writes:
On Monday, 17 June 2013 at 13:59:34 UTC, Artur Skawina wrote:
 [..]
Your setup has a pretty serious issue with correctness though. It's because all the types of _ForEach.tuple are the same as the first element of TS... import std.stdio; template NTup(size_t N, T...) { static if (N > 1) alias NTup = NTup!(N-1, T, T[$-1]); else alias NTup = T; } struct _ForEach(alias MAP, TS...) { NTup!(TS.length, typeof(MAP(TS[0].init))) tuple; this(TS values) { foreach (i, ref v; values) tuple[i] = MAP(v); } } auto ForEach(alias MAP, TS...)(TS ts) { return _ForEach!(MAP, TS)(ts); } void foo(T...)(T values) { foreach (v; values) writeln(v); } void bar(T...)(T values) { foo(ForEach!(a => a + 1)(values).tuple); } void main() { bar(10, 3_000_000_000u); } ----- Prints: 11 -1294967295 Change the call to bar(1, 3L); and it wouldn't even compile.
Jun 17 2013
parent "TommiT" <tommitissari hotmail.com> writes:
On Tuesday, 18 June 2013 at 02:37:46 UTC, TommiT wrote:
 It's because all the types of _ForEach.tuple are the same as 
 the first element of TS...
I mean... the same as the type of MAP(TS[0])
Jun 17 2013
prev sibling parent =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
On 06/17/2013 02:32 AM, TommiT wrote:

 On Monday, 17 June 2013 at 07:20:23 UTC, Ali Çehreli wrote:
 The following does not answer the question of expanding but at least
 foo() receives [30, 70, 110] :)

 import std.stdio;
 import std.algorithm;
 import std.array;
 import std.range;

 int[] arr = [ 1, 3, 5, 7, 11 ];

 void foo(T)(T[] values...)
 {
     writeln(values);
 }

 void bar(T)(T[] values...)
 {
     foo(arr
         .indexed(values)
         .map!(a => a * 10)
         .array);
 }

 void main()
 {
     bar(1, 3, 4);
 }

 Ali
Yeah, that would work. I'd hate the overhead though.
There is no inherent overhead though. I called .array only because I thought that the C++ version was eager. If we stay lazy and D-like, we can make foo() take a range: import std.stdio; import std.algorithm; import std.range; int[] arr = [ 1, 3, 5, 7, 11 ]; void foo(R)(R range) // <-- now takes range { writeln(range); } void bar(T)(T[] values...) { foo(arr .indexed(values) .map!(a => a * 10)); // <-- no .array anymore } void main() { bar(1, 3, 4); } I hear that ldc (and perhaps gdc) perform pretty magical compiler optimizations. There is no reason for the code above to be slower than the hand-written equivalent. Ali
Jun 17 2013