digitalmars.D.learn - Variadic template arguments unpacking
- Max Strakhov (26/26) Jul 05 2013 Suppose, in c++ i've got a variadic template function F, and i
- =?UTF-8?B?QWxpIMOHZWhyZWxp?= (22/48) Jul 05 2013 Here is one that works:
- =?UTF-8?B?QWxpIMOHZWhyZWxp?= (4/6) Jul 05 2013 And the definitive document on templates is the following:
- Max Strakhov (18/35) Jul 06 2013 You only converted the format string with was not in the
- Artur Skawina (3/4) Jul 06 2013 http://forum.dlang.org/thread/yiougbmcegjlsrynlvyt@forum.dlang.org?page=...
- Max Strakhov (8/8) Jul 06 2013 Artur, if i use your solution like
- Artur Skawina (3/8) Jul 06 2013 Can you show some simple code that exhibits the problem?
- Max Strakhov (8/8) Jul 06 2013 Thanx for all your help, i just solved the problem with mixins
- TommiT (7/15) Jul 06 2013 printf("...", ForEach!(val => conv(val).c_str())(values).tuple);
- TommiT (2/4) Jul 06 2013 Should be "...one field per each of those..."
- TommiT (46/60) Jul 06 2013 import std.stdio;
- Max Strakhov (4/5) Jul 06 2013 Yeah, i fixed them myself. Still, there were some problems.
- Artur Skawina (9/32) Jul 06 2013 This is slicing a local static array and returning that slice,
- TommiT (7/10) Jul 06 2013 Yes, but certainly simple parameter pack expansion shouldn't be
- Max Strakhov (40/42) Jul 06 2013 I wrote a template, that can do something like that:
- Artur Skawina (68/76) Jul 07 2013 Well, you could just use the pseudo-code version that you posted,
- TommiT (47/48) Jul 06 2013 Can you tell me why this isn't working though?
-
TommiT
(47/48)
Jul 06 2013
I can get this so far that it prints: 1 1 2 0
- TommiT (7/15) Jul 06 2013 I thought about this a bit more and realised you're right. The
- Max Strakhov (4/9) Jul 06 2013 I had the same idea too, but couldn't make it work: compiler
- TommiT (10/18) Jul 06 2013 There were a couple of typos in _TypeMap. That's a correct one:
Suppose, in c++ i've got a variadic template function F, and i want to convert all the arguments in some individual manner by function conv and pass all the converted arguments as a variadic list to another variadic template function G. Then i just do the following: template<typename T> struct R; template<typename T> R<T>::Type conv(T&& val); template<typename ... Args> void F(Args&& ... args) { G(conv(std::forward<Args>(args))...); } Is there any way to do the same kind of thing in D? I couldn't find any unpacking operator or a library function to do so... The problem is that i have to pass converted lits to a C-like variadic function, for example printf, so it would be like: template<typename T> struct R; template<typename T> R<T>::Type conv(T&& val); template<typename ... Args> void MyPrintf(const char * format, Args&& ... args) { printf(convFormat(format), conv(std::forward<Args>(args))...); } I hope, my point is clear enough.
Jul 05 2013
On 07/05/2013 05:41 PM, Max Strakhov wrote:Suppose, in c++ i've got a variadic template function F, and i want to convert all the arguments in some individual manner by function conv and pass all the converted arguments as a variadic list to another variadic template function G. Then i just do the following: template<typename T> struct R; template<typename T> R<T>::Type conv(T&& val); template<typename ... Args> void F(Args&& ... args) { G(conv(std::forward<Args>(args))...); } Is there any way to do the same kind of thing in D? I couldn't find any unpacking operator or a library function to do so... The problem is that i have to pass converted lits to a C-like variadic function, for example printf, so it would be like: template<typename T> struct R; template<typename T> R<T>::Type conv(T&& val); template<typename ... Args> void MyPrintf(const char * format, Args&& ... args) { printf(convFormat(format), conv(std::forward<Args>(args))...); } I hope, my point is clear enough.Here is one that works: import core.stdc.stdio; import std.string; string convFormat(string s) { return s ~ '\n'; } void myPrintf(T...)(string s, T args) { printf(convFormat(s).toStringz, args); } void main() { myPrintf("format %d %f %c %s", 42, 1.5, 'a', "hello".toStringz); } Ideally, the caller of MyPrintf should not have to call toStringz on "hello", because myPrintf is a proper D function. However, I don't know how to manipulate args to call toStringz on string arguments. More information is at http://dlang.org/template.html#TemplateTupleParameter Ali
Jul 05 2013
On 07/05/2013 06:35 PM, Ali Çehreli wrote:More information is at http://dlang.org/template.html#TemplateTupleParameterAnd the definitive document on templates is the following: https://github.com/PhilippeSigaud/D-templates-tutorial Ali
Jul 05 2013
On Saturday, 6 July 2013 at 01:35:28 UTC, Ali Çehreli wrote:On 07/05/2013 05:41 PM, Max Strakhov wrote: Here is one that works: import core.stdc.stdio; import std.string; string convFormat(string s) { return s ~ '\n'; } void myPrintf(T...)(string s, T args) { printf(convFormat(s).toStringz, args); } void main() { myPrintf("format %d %f %c %s", 42, 1.5, 'a', "hello".toStringz); }You only converted the format string with was not in the question. I need to convert all args by a template function, but your example is actually better than mine. Suppose, i want my own string representation of primitive types, then in c++ i would write those two functions: convFormat("format %d %f %c %s") -> std::string("format %s %s %s %s") conv(42) -> std::string("int(42)") conv(1.5)-> std::string("float(1.5)") etc. and the resulting printf: template<typename ... Args> void myPrintf(const char * format, Args&& ... args) { printf(convFormat(format).c_str(), conv(std::forward<Args>(args)).c_str()...); } And that would do the job. Can i somehow do the same thing in D?
Jul 06 2013
On 07/06/13 02:41, Max Strakhov wrote:Suppose, in c++ i've got a variadic template function F, and i want to convert all the arguments in some individual manner by function conv and pass all the converted arguments as a variadic list to another variadic template function G.http://forum.dlang.org/thread/yiougbmcegjlsrynlvyt forum.dlang.org?page=2#post-mailman.1205.1371553020.13711.digitalmars-d-learn:40puremagic.com artur
Jul 06 2013
Artur, if i use your solution like printf("...", ForEach!(val => conv(val).c_str())(values).tuple); Than i would get a crash, because all the tuple elements would be char*'s, pointing to already freed memory, as std::string's destructor gets called each time right after alias function exits. Ths is what c++ unpacking operator for: i actually return a packed list of std::string's, apply .c_str() to each and than unpack the list to current context.
Jul 06 2013
On 07/06/13 14:21, Max Strakhov wrote:Artur, if i use your solution like printf("...", ForEach!(val => conv(val).c_str())(values).tuple); Than i would get a crash, because all the tuple elements would be char*'s, pointing to already freed memory, as std::string's destructor gets called each time right after alias function exits. Ths is what c++ unpacking operator for: i actually return a packed list of std::string's, apply .c_str() to each and than unpack the list to current context.Can you show some simple code that exhibits the problem? artur
Jul 06 2013
Thanx for all your help, i just solved the problem with mixins like this: string res = ""; for(i, rev v; args) res ~= string.format(", f(args[%d]).val()"); mixin("return G(" ~ res ~ ");"); Only without any actual looping and variables, so compiler could deal with it.
Jul 06 2013
On Saturday, 6 July 2013 at 16:15:52 UTC, Max Strakhov wrote:Thanx for all your help, i just solved the problem with mixins like this: string res = ""; for(i, rev v; args) res ~= string.format(", f(args[%d]).val()"); mixin("return G(" ~ res ~ ");"); Only without any actual looping and variables, so compiler could deal with it.printf("...", ForEach!(val => conv(val).c_str())(values).tuple); ...would have done exactly the same as C++: printf("...", conv(values).c_str()...); ...does (barring it makes a temporary struct which holds one field for each of those converted const char pointers, but the compiler can optimize it away).
Jul 06 2013
On Saturday, 6 July 2013 at 16:40:25 UTC, TommiT wrote:...does (barring it makes a temporary struct which holds one field for each of those converted const char pointers, ...Should be "...one field per each of those..."
Jul 06 2013
On Saturday, 6 July 2013 at 15:23:40 UTC, Artur Skawina wrote:On 07/06/13 14:21, Max Strakhov wrote: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+1..$]); 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); } int[2] getArray(int n) { int[2] data = n; return data; } void foo(R...)(R ranges) { foreach (range; ranges) foreach (value; range) write(value, " "); } void main() { // Expecting this to print: 1 1 2 2 3 3 foo(ForEach!(a => getArray(a)[])(1, 2, 3).tuple); // ... but it prints random garbage } Notice that there were a couple of typos in _TypeMap.Artur, if i use your solution like printf("...", ForEach!(val => conv(val).c_str())(values).tuple); Than i would get a crash, because all the tuple elements would be char*'s, pointing to already freed memory, as std::string's destructor gets called each time right after alias function exits. Ths is what c++ unpacking operator for: i actually return a packed list of std::string's, apply .c_str() to each and than unpack the list to current context.Can you show some simple code that exhibits the problem? artur
Jul 06 2013
There were a couple of typos in _TypeMap.Yeah, i fixed them myself. Still, there were some problems. I like my solution better anyway, because it behaves exactly like c++ ... operator and just expands some code for all items in tuple.
Jul 06 2013
On 07/06/13 19:42, TommiT wrote:TS[N+1..$]);Notice that there were a couple of typos in _TypeMap.Thanks for catching that.int[2] getArray(int n) { int[2] data = n; return data; } void foo(R...)(R ranges) { foreach (range; ranges) foreach (value; range) write(value, " "); } void main() { // Expecting this to print: 1 1 2 2 3 3 foo(ForEach!(a => getArray(a)[])(1, 2, 3).tuple); // ... but it prints random garbage }This is slicing a local static array and returning that slice, which points to the stack and lives only until the lambda returns. Moving the data to the heap or simply returning it directly (ie by value) will work:foo(ForEach!(a => getArray(a).dup[])(1, 2, 3).tuple); foo(ForEach!(a => getArray(a))(1, 2, 3).tuple);I like Max's solution better than mine too, btw. Why didn't i think of that... :) artur
Jul 06 2013
On Saturday, 6 July 2013 at 18:26:28 UTC, Artur Skawina wrote:This is slicing a local static array and returning that slice, which points to the stack and lives only until the lambda returns.Yes, but certainly simple parameter pack expansion shouldn't be this brittle. I want to be able to write: foo(getArray(v)[]...); // v is a parameter pack (1, 2, 3) And be confident in that it gets simply re-written as: foo(getArray(v[0])[], getArray(v[1])[], getArray(v[2])[]); It's easy to conceptualise and easy to get it right.
Jul 06 2013
foo(getArray(v[0])[], getArray(v[1])[], getArray(v[2])[]); It's easy to conceptualise and easy to get it right.I wrote a template, that can do something like that: template Expand(string code, string Args, string args, Ts...) { const hasType = std.string.indexOf(code, "%Arg") != -1; const hasVal = std.string.indexOf(code, "%arg") != -1; static if(hasType) const code1 = std.array.replace(code, "%Arg", Args ~ "[%d]"); else const code1 = code; static if(hasVal) const code2 = std.array.replace(code1, "%arg", args ~ "[%d]"); else const code2 = code1; template Fun(size_t i, T) { static if(!hasType && !hasVal) alias Fun = TypeTuple!(code2); else static if(hasType && hasVal) alias Fun = TypeTuple!(std.string.format(code2, i, i)); else alias Fun = TypeTuple!(std.string.format(code2, i)); } template ConcatArgs() { const Val = ""; } template ConcatArgs(T, As...) if(!is(T == string)) { static assert(T); } template ConcatArgs(string s, As...) if(As.length == 0) { const Val = s; } template ConcatArgs(string s, As...) if(As.length != 0) { const Val = s ~ ", " ~ ConcatArgs!As.Val; } const Val = ConcatArgs!(staticIndexedMap!(Fun, Ts)).Val; } Usage: writeln(Expand!("getArray(%arg)[]", "", "v", string, int, int).Val); // == getArray(v[0])[], getArray(v[1])[], getArray(v[2])[] If anyine has any ideas how make this implementation simplier, bring it on :)
Jul 06 2013
On 07/06/13 21:39, Max Strakhov wrote:If anyine has any ideas how make this implementation simplier, bring it on :)Well, you could just use the pseudo-code version that you posted, almost verbatim:string res = ""; for(i, rev v; args) res ~= string.format(", f(args[%d]).val()"); mixin("return G(" ~ res ~ ");"); Only without any actual looping and variables, so compiler could deal with it.auto F(A...)(string format, A args) { mixin({ string res; foreach (I, _; A) res ~= (I?", ":"") ~ "conv(args["~I.stringof~"])"; return "return G(convformat(format), " ~ res ~ ");"; }()); } The compiler can deal with this, no need to make it more complicated. It's a good idea for non-obvious text-as-code manipulations to be explicit, so I'd actually do it like that. A problem when manipulating source code directly is that not everything can be referred to by name - a non-local type or symbol which isn't available in the current scope will work when it's a template parameter or via typeof(), but you can't directly name it and then mix it back in. Locals, including function args, will work though. For cases where the above restriction isn't a problem, we could use something more generic, that will make it possible to solve your problem with a one-liner. It will work with not just "argument-packs" (ie arg-tuples), but also some symbols, types and statically evaluable expressions. The "oops" line shows what does *not* work and why these kinds of mixin tricks should be used only when you have control over all args and users. template evalExpMap(string C, string F, A...) { enum evalExpMap = { import std.array; string s; static if (is(typeof(A))) alias B = typeof(A); else alias B = A; foreach (I, _; B) s ~= (I?", ":"") ~ replace(F, "%", A[I].stringof); return replace(C, "%", s); }(); } import std.stdio; void main() { auto c = mixin (evalExpMap!(q{ foo(%) }, q{ getArray(%)[] }, 1, "two", 3.14)); b1(c, 3_000_000_000u, 2.14, -43L); auto s = S!(int, float, "/4", "blah")(); writeln(typeof(s.data).stringof, typeof(s.blah).stringof); //auto oops = S!(typeof(as()), float, "/4", "blah")(); } auto getArray(T)(T n) { T[2] data = n; return data; } auto foo(A...)(A args) { int c; foreach (rs; args) foreach (v; rs) ++c, write(v, " "); return c; } void b1(T...)(T values) { mixin (evalExpMap!(q{b2('\n',1,2,3,%);}, q{%+1}, values)); } void b2(T...)(T vs) { foreach (v; vs) writeln(v); } template Q(A...) { alias Q=A; } struct S(A...) { mixin (evalExpMap!(q{alias T=Q!(%);}, q{%[16]}, A[0..$-2])); T data; mixin (evalExpMap!(q{Q!(%) }~A[3]~q{;}, q{%[16}~A[$-2]~q{]}, const A[1], shared(A[0])*)); } auto as() { struct An {} return An(); } artur
Jul 07 2013
On Saturday, 6 July 2013 at 15:23:40 UTC, Artur Skawina wrote:...Can you tell me why this isn't working though? import std.stdio, std.typetuple; 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; } 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, v; values) tuple[i] = MAP(v); } } auto ForEach(alias MAP, TS...)(TS ts) { return _ForEach!(MAP, TS)(ts); } int[2] getArray(int n) { int[2] data = n; return data; } void foo(R...)(R ranges) { foreach (range; ranges) foreach (value; range) write(value, " "); } void main() { alias pack = TypeTuple!(1, 2, 3); // prints random garbage foo(ForEach!(tmp => tmp[]) (ForEach!(a => getArray(a))(pack).tuple).tuple); }
Jul 06 2013
On Saturday, 6 July 2013 at 18:17:34 UTC, TommiT wrote:Can you tell me why this isn't working though?I can get this so far that it prints: 1 1 2 0 <random> <random> import std.stdio, std.typetuple; 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]][0])), TS[N+1..$]); 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, v; values) tuple[i] = MAP(v); } } auto ForEach(alias MAP, TS...)(TS ts) { return _ForEach!(MAP, TS)(ts); } int[2] getArray(int n) { int[2] data = n; return data; } void foo(R...)(R ranges) { foreach (range; ranges) foreach (value; range) write(value, " "); } void main() { alias pack = TypeTuple!(1, 2, 3); // prints random garbage foo(ForEach!((ref int[2] tmp) { return tmp[]; }) (ForEach!(a => getArray(a))(pack).tuple).tuple); }
Jul 06 2013
On Saturday, 6 July 2013 at 12:21:29 UTC, Max Strakhov wrote:Artur, if i use your solution like printf("...", ForEach!(val => conv(val).c_str())(values).tuple); Than i would get a crash, because all the tuple elements would be char*'s, pointing to already freed memory, as std::string's destructor gets called each time right after alias function exits. Ths is what c++ unpacking operator for: i actually return a packed list of std::string's, apply .c_str() to each and than unpack the list to current context.I thought about this a bit more and realised you're right. The following should work though: printf( "...", ForEach!(tmp => tmp.c_str())(ForEach!(val => conv(val))(values).tuple).tuple ); It's a defect in ForEach though, and gives me more reason to ask for the C++ style ellipsis expansion operator for D.
Jul 06 2013
The following should work though: printf( "...", ForEach!(tmp => tmp.c_str())(ForEach!(val => conv(val))(values).tuple).tuple );I had the same idea too, but couldn't make it work: compiler complains about failed CTFE or something. On the other hand i might do somethong wrong.It's a defect in ForEach though, and gives me more reason to ask for the C++ style ellipsis expansion operator for D.Yeap, i personally find (as * 2)... awesome feature of c++.
Jul 06 2013
On Saturday, 6 July 2013 at 17:29:36 UTC, Max Strakhov wrote:There were a couple of typos in _TypeMap. That's a 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; }The following should work though: printf( "...", ForEach!(tmp => tmp.c_str())(ForEach!(val => conv(val))(values).tuple).tuple );I had the same idea too, but couldn't make it work: compiler complains about failed CTFE or something. On the other hand i might do somethong wrong.Yeap, i personally find (as * 2)... awesome feature of c++.Me too.
Jul 06 2013