digitalmars.D.learn - How to expand an expression along with a parameter tuple?
- TommiT (59/59) Jun 16 2013 I can't figure out how to do the following C++ code in D:
- TommiT (13/13) Jun 16 2013 Although... now that I think about it, this should really be done
- =?UTF-8?B?QWxpIMOHZWhyZWxp?= (24/40) Jun 17 2013 The following does not answer the question of expanding but at least
- TommiT (2/25) Jun 17 2013 Yeah, that would work. I'd hate the overhead though.
- Artur Skawina (8/40) Jun 17 2013 void bar(T...)(T values) {
- TommiT (3/44) Jun 17 2013 Cool, I didn't know that you could create multiple variables like
- Artur Skawina (21/30) Jun 17 2013 A more correct, but a bit less readable version (the types of 'values' a...
- TommiT (10/31) Jun 17 2013 Argh, that's a lot of boilerplate. Thanks for pointing this out.
- Artur Skawina (29/59) Jun 17 2013 Well, the only real difference between these two examples is
- bearophile (5/9) Jun 17 2013 Implicit things are dangerous in languages.
- Artur Skawina (19/26) Jun 17 2013 Not sure what you mean.
- bearophile (5/7) Jun 17 2013 If in the code you wrote you replace the first ".tuple" with "[]"
- Artur Skawina (5/12) Jun 17 2013 It does not - I really have no idea what you mean; slicing a struct
- bearophile (4/8) Jun 18 2013 Sorry, I have misunderstood the type, the [] works on typetuples.
- TommiT (7/32) Jun 17 2013 Now, this is pretty cool. But I wonder a couple of things:
- Artur Skawina (44/69) Jun 18 2013 Not a significant one, I'd expect; for simple cases like these it should...
- TommiT (43/44) Jun 17 2013 Your setup has a pretty serious issue with correctness though.
- TommiT (2/4) Jun 17 2013 I mean... the same as the type of MAP(TS[0])
- =?UTF-8?B?QWxpIMOHZWhyZWxp?= (26/58) Jun 17 2013 There is no inherent overhead though. I called .array only because I
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
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
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
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); } AliYeah, that would work. I'd hate the overhead though.
Jun 17 2013
On 06/17/13 11:32, TommiT wrote:On Monday, 17 June 2013 at 07:20:23 UTC, Ali Çehreli wrote:void bar(T...)(T values) { T tmp; foreach (i, ref v; values) tmp[i] = arr[v]*10; foo(tmp); } arturThe 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); } AliYeah, that would work. I'd hate the overhead though.
Jun 17 2013
On Monday, 17 June 2013 at 11:15:24 UTC, Artur Skawina wrote:On 06/17/13 11:32, TommiT wrote:Cool, I didn't know that you could create multiple variables like that (T tmp).On Monday, 17 June 2013 at 07:20:23 UTC, Ali Çehreli wrote:void bar(T...)(T values) { T tmp; foreach (i, ref v; values) tmp[i] = arr[v]*10; foo(tmp); } arturThe 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); } AliYeah, that would work. I'd hate the overhead though.
Jun 17 2013
On 06/17/13 13:23, TommiT wrote:On Monday, 17 June 2013 at 11:15:24 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; } arturvoid 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).
Jun 17 2013
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; } arturArgh, 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
On 06/17/13 14:57, TommiT wrote:On Monday, 17 June 2013 at 12:21:31 UTC, Artur Skawina wrote: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)...); arturA 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)...); }
Jun 17 2013
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
On 06/17/13 16:20, bearophile wrote:Artur Skawina: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.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 "[]".No idea what you mean by this. artur
Jun 17 2013
Artur Skawina:If in the code you wrote you replace the first ".tuple" with "[]" the code keeps working. Bye, bearophile".tuple" can also be written "[]".No idea what you mean by this.
Jun 17 2013
On 06/17/13 23:11, bearophile wrote:Artur Skawina: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... arturIf in the code you wrote you replace the first ".tuple" with "[]" the code keeps working.".tuple" can also be written "[]".No idea what you mean by this.
Jun 17 2013
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
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)...); arturNow, 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
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
On Tuesday, 18 June 2013 at 10:57:00 UTC, Artur Skawina wrote:On 06/18/13 03:51, TommiT wrote: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.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
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
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
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
On 06/17/2013 02:32 AM, TommiT wrote:On Monday, 17 June 2013 at 07:20:23 UTC, Ali Çehreli wrote: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. AliThe 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); } AliYeah, that would work. I'd hate the overhead though.
Jun 17 2013