www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Typed variadic template syntax?

reply "bearophile" <bearophileHUGS lycos.com> writes:
This is not an enhancement request, but it presents a potential 
enhancement.


This is C++11 code:

template<int... digits> struct value;

template<> struct value<> {
     static const int v = 0;
};

template<int first, int... rest> struct value<first, rest...> {
     static int const v = 10 * value<rest...>::v + first;
};



You can translate it to D like this:


enum isInt(T) = is(T == int);

template value(xs...) if (allSatisfy!(isInt, xs)) {
     static if (xs.length == 0)
         enum value = 0;
     else
         enum value = xs[0] + 10 * value!(xs[1 .. $]);
}


This D code:

template value(xs...) if (allSatisfy!(isInt, xs)) {


Is slower to compile than this D code:

template value(xs...) {


So we could allow D code like:

template value(int[] xs...) {


I think it could be faster than using "if (allSatisfy!(isInt, 
xs))" and it's nicer looking.

On the other hand I don't know how much common are template 
instantiations with values all of the same type (like all ints as 
in this case) in D code.

Opinions welcome.

Bye,
bearophile
Jan 28 2014
next sibling parent "Stanislav Blinov" <stanislav.blinov gmail.com> writes:
On Wednesday, 29 January 2014 at 00:14:31 UTC, bearophile wrote:

 On the other hand I don't know how much common are template 
 instantiations with values all of the same type (like all ints 
 as in this case) in D code.

 Opinions welcome.

 Bye,
 bearophile

Well, if you're going for a direct translation, that would be more like template value(ints...) { static if (!ints.length) enum value = 0; else static if (!is(ints[0] == int)) static assert(false); else enum value = ints[0] + 10*value!(ints[1..$]); } But that indeed illustrates a certain disconcert: we can already have variadic functions of the same type (void foo(int[] a...)). It would certainly be nice to have the same for template parameters.
Jan 28 2014
prev sibling next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 1/28/14 4:14 PM, bearophile wrote:
 This is not an enhancement request, but it presents a potential
 enhancement.


 This is C++11 code:

 template<int... digits> struct value;

 template<> struct value<> {
      static const int v = 0;
 };

 template<int first, int... rest> struct value<first, rest...> {
      static int const v = 10 * value<rest...>::v + first;
 };



 You can translate it to D like this:


 enum isInt(T) = is(T == int);

 template value(xs...) if (allSatisfy!(isInt, xs)) {
      static if (xs.length == 0)
          enum value = 0;
      else
          enum value = xs[0] + 10 * value!(xs[1 .. $]);
 }

int value(int xs[]...) { int result = 0; foreach (i; xs) { result += 10 * result + i; } return result; } unittest { static assert(value(1, 2) == 21); } Andrei
Jan 28 2014
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 01/29/2014 08:01 AM, Andrei Alexandrescu wrote:
 int value(int xs[]...) {
    int result = 0;
    foreach (i; xs) {
      result += 10 * result + i;
    }
    return result;
 }

 unittest
 {
    static assert(value(1, 2) == 21);
 }
 ...

import std.range, std.algorithm; int value(int xs[]...) { return reduce!((a,b)=>10*a+b)(0,xs.retro); } unittest{ static assert(value()==0); static assert(value(1)==1); static assert(value(1,2)==21); static assert(value(1,2,3)==321); }
Jan 29 2014
next sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 1/29/14 1:53 AM, Stanislav Blinov wrote:
 On Wednesday, 29 January 2014 at 09:50:13 UTC, Timon Gehr wrote:

 import std.range, std.algorithm;
 int value(int xs[]...) {
     return reduce!((a,b)=>10*a+b)(0,xs.retro);
 }

Sadly, you can't build a struct (with N int fields) or an enum this way.

mixin Andrei
Jan 29 2014
prev sibling next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 01/30/2014 03:08 PM, Ilya Yaroshenko wrote:
 2) `foreach` creates it's own scope. This won't work:
     foreach(i; TypeTuple!(1,2,3)){
         mixin("int num"~i.stringof~";");
     }
     num1=1;
     num2=2;
     num3=3;
     writeln(num1,num2,num3);

... 2) no. This should work for compile time foreach and TypeTuples. There are many examples in source code of Phobos.

The following code fails to compile: import std.typetuple, std.stdio; void main(){ foreach(i; TypeTuple!(1,2,3)){ mixin("int num"~i.stringof~";"); } num1=1; num2=2; num3=3; writeln(num1,num2,num3); }
Jan 30 2014
parent Etienne <etcimon gmail.com> writes:
On 2014-01-30 10:28 AM, Timon Gehr wrote:
 import std.typetuple, std.stdio;
 void main(){
      foreach(i; TypeTuple!(1,2,3)){
          mixin("int num"~i.stringof~";");
      }
      num1=1;
      num2=2;
      num3=3;
      writeln(num1,num2,num3);
 }

This written as a static foreach or declarative foreach, would be awesome if it exposed the scope. For now the only solution is to build a string and mixin the string like this string fct(R)(){ string str; foreach (range ; R) str ~= "int num " ~ range.stringof ~ ";"; return str; } mixin(fct!(TypeTuple!(1,2,3)); writeln(num1,num2, num3); Looks a little less convenient than static foreach ( i; 0..3 ) mixin("int num" ~ i.stringof ~ ";"); writeln(num1,num2, num3);
Jan 30 2014
prev sibling next sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 1/30/14 12:33 PM, Dicebot wrote:
 Andrei has stated at least once that he agrees about usefulness /
 necessity of declaration foreach. It is mostly matter of someone doing
 implementation, same as for many other "hot" discussed stuff.

Yah, we should have an easier means of iteratively injecting stuff into a scope. Andrei
Jan 30 2014
prev sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 01/31/2014 03:32 AM, Idan Arye wrote:

mixin({ foreach(...) { ... } }())

Except it doesn't work inside classes and structs - it complains that "function literals cannot be class members". You have to define a named function and pollute the namespace.

This is the corresponding issue: https://d.puremagic.com/issues/show_bug.cgi?id=7653
Jan 31 2014
prev sibling next sibling parent "Jakob Ovrum" <jakobovrum gmail.com> writes:
On Wednesday, 29 January 2014 at 07:02:00 UTC, Andrei 
Alexandrescu wrote:
 int value(int xs[]...) {

Ew! :)
Jan 28 2014
prev sibling next sibling parent "Stanislav Blinov" <stanislav.blinov gmail.com> writes:
On Wednesday, 29 January 2014 at 09:50:13 UTC, Timon Gehr wrote:

 import std.range, std.algorithm;
 int value(int xs[]...) {
     return reduce!((a,b)=>10*a+b)(0,xs.retro);
 }

Sadly, you can't build a struct (with N int fields) or an enum this way.
Jan 29 2014
prev sibling next sibling parent "Stanislav Blinov" <stanislav.blinov gmail.com> writes:
On Wednesday, 29 January 2014 at 16:39:31 UTC, Andrei 
Alexandrescu wrote:
 On 1/29/14 1:53 AM, Stanislav Blinov wrote:

 Sadly, you can't build a struct (with N int fields) or an enum 
 this way.

mixin

m|
Jan 29 2014
prev sibling next sibling parent "Etienne" <etcimon gmail.com> writes:
On Wednesday, 29 January 2014 at 09:53:15 UTC, Stanislav Blinov 
wrote:
 On Wednesday, 29 January 2014 at 09:50:13 UTC, Timon Gehr wrote:

 import std.range, std.algorithm;
 int value(int xs[]...) {
    return reduce!((a,b)=>10*a+b)(0,xs.retro);
 }

Sadly, you can't build a struct (with N int fields) or an enum this way.

That was a problem for me today XD . Couldn't make a auto fct(string[] choices ...)(string[] id, string[] key ...) but I managed to find a workaround... // KEYWORDS enum USING = "USING.", VAR = "VAR.", LIST = "LIST.", SET = "SET.", ZSET = "ZSET.", HASHMAP = "HASHMAP.", LOCK = "LOCK.", UNLOCK = "UNLOCK.", SKIPCACHE = "SKIPCACHE.", INCREMENT = "INCREMENT.", DECREMENT = "DECREMENT.", FRONT = "FRONT.", BACK = "BACK.", PUSH = "PUSH.", POP = "POP.", REPLACE = "REPLACE.", TRIM = "TRIM.", RETURNS = "RETURNS.", LENGTH = "LENGTH.", EXISTS = "EXISTS.", VALUE = "VALUE.", KEY = "KEY.", STREAM = "STREAM.", ERROR = "ERROR.", void exec(string command)(){ foreach(str ; choice.splitter(".")){ writeln(str); } } void main() { exec!(USING ~ VAR ~ ADD ~ USING ~ LIST ~ INSERT ~ LOCK)(); }
Jan 29 2014
prev sibling next sibling parent "Etienne" <etcimon gmail.com> writes:
 void exec(string command)(){
 	foreach(str ; choice.splitter(".")){
 		writeln(str);
 	}
 }

I'd like to take the opportunity to say how much I'd love to be able to do a static foreach rather than use recursion.
Jan 29 2014
prev sibling next sibling parent "Peter Alexander" <peter.alexander.au gmail.com> writes:
On Wednesday, 29 January 2014 at 21:18:06 UTC, Etienne wrote:
 void exec(string command)(){
 	foreach(str ; choice.splitter(".")){
 		writeln(str);
 	}
 }

I'd like to take the opportunity to say how much I'd love to be able to do a static foreach rather than use recursion.

You can use TypeTuple for static foreach. void foo(int x)() { import std.stdio; writeln(x); } void main() { import std.typetuple; foreach (x; TypeTuple!(1, 2, 3)) foo!x(); } Notice that the loop variable x is used as a template parameter at compile time. The code expands to: foo!1(); foo!2(); foo!3();
Jan 29 2014
prev sibling next sibling parent "inout" <inout gmail.com> writes:
On Wednesday, 29 January 2014 at 07:02:00 UTC, Andrei 
Alexandrescu wrote:
 On 1/28/14 4:14 PM, bearophile wrote:
 This is not an enhancement request, but it presents a potential
 enhancement.


 This is C++11 code:

 template<int... digits> struct value;

 template<> struct value<> {
     static const int v = 0;
 };

 template<int first, int... rest> struct value<first, rest...> {
     static int const v = 10 * value<rest...>::v + first;
 };



 You can translate it to D like this:


 enum isInt(T) = is(T == int);

 template value(xs...) if (allSatisfy!(isInt, xs)) {
     static if (xs.length == 0)
         enum value = 0;
     else
         enum value = xs[0] + 10 * value!(xs[1 .. $]);
 }

int value(int xs[]...) { int result = 0; foreach (i; xs) { result += 10 * result + i; } return result; } unittest { static assert(value(1, 2) == 21); } Andrei

This allocates memory and as such an inferior solution. I'd prefer ugly code over this any day.
Jan 29 2014
prev sibling next sibling parent "anonymous" <anonymous example.com> writes:
On Wednesday, 29 January 2014 at 22:25:45 UTC, inout wrote:
 int value(int xs[]...) {
  int result = 0;
  foreach (i; xs) {
 	result += 10 * result + i;
  }
  return result;
 }

 unittest
 {
  static assert(value(1, 2) == 21);
 }


 Andrei

This allocates memory

no
Jan 29 2014
prev sibling next sibling parent "Idan Arye" <GenericNPC gmail.com> writes:
On Wednesday, 29 January 2014 at 22:16:57 UTC, Peter Alexander 
wrote:
 On Wednesday, 29 January 2014 at 21:18:06 UTC, Etienne wrote:
 void exec(string command)(){
 	foreach(str ; choice.splitter(".")){
 		writeln(str);
 	}
 }

I'd like to take the opportunity to say how much I'd love to be able to do a static foreach rather than use recursion.

You can use TypeTuple for static foreach. void foo(int x)() { import std.stdio; writeln(x); } void main() { import std.typetuple; foreach (x; TypeTuple!(1, 2, 3)) foo!x(); } Notice that the loop variable x is used as a template parameter at compile time. The code expands to: foo!1(); foo!2(); foo!3();

Two problems: 1) You can't use `foreach` outside functions. That means that you write: struct Foo{ foreach(i;TypeTuple!(1,2,3)){ mixin("int num"~i.stringof~";"); } } 2) `foreach` creates it's own scope. This won't work: foreach(i; TypeTuple!(1,2,3)){ mixin("int num"~i.stringof~";"); } num1=1; num2=2; num3=3; writeln(num1,num2,num3);
Jan 30 2014
prev sibling next sibling parent "Ilya Yaroshenko" <ilyayaroshenko gmail.com> writes:
On Thursday, 30 January 2014 at 11:19:33 UTC, Idan Arye wrote:
 On Wednesday, 29 January 2014 at 22:16:57 UTC, Peter Alexander 
 wrote:
 On Wednesday, 29 January 2014 at 21:18:06 UTC, Etienne wrote:
 void exec(string command)(){
 	foreach(str ; choice.splitter(".")){
 		writeln(str);
 	}
 }

I'd like to take the opportunity to say how much I'd love to be able to do a static foreach rather than use recursion.

You can use TypeTuple for static foreach. void foo(int x)() { import std.stdio; writeln(x); } void main() { import std.typetuple; foreach (x; TypeTuple!(1, 2, 3)) foo!x(); } Notice that the loop variable x is used as a template parameter at compile time. The code expands to: foo!1(); foo!2(); foo!3();

Two problems: 1) You can't use `foreach` outside functions. That means that you write: struct Foo{ foreach(i;TypeTuple!(1,2,3)){ mixin("int num"~i.stringof~";"); } } 2) `foreach` creates it's own scope. This won't work: foreach(i; TypeTuple!(1,2,3)){ mixin("int num"~i.stringof~";"); } num1=1; num2=2; num3=3; writeln(num1,num2,num3);

1) You can use mixin(format("<Prolog>%(<Begin>%s<End>%)<Epilog>", CompileTimeRange)); See std.format for ranges and std.string.format. 2) no. This should work for compile time foreach and TypeTuples. There are many examples in source code of Phobos.
Jan 30 2014
prev sibling next sibling parent "Dicebot" <public dicebot.lv> writes:
On Wednesday, 29 January 2014 at 22:16:57 UTC, Peter Alexander 
wrote:
 You can use TypeTuple for static foreach.

 ...

This is why I think using term "declaration foreach" is less confusing. It is pretty much the same but shifts attention away from essentional use case.
Jan 30 2014
prev sibling next sibling parent "Ilya Yaroshenko" <ilyayaroshenko gmail.com> writes:
On Thursday, 30 January 2014 at 15:28:34 UTC, Timon Gehr wrote:
 On 01/30/2014 03:08 PM, Ilya Yaroshenko wrote:
 2) `foreach` creates it's own scope. This won't work:
    foreach(i; TypeTuple!(1,2,3)){
        mixin("int num"~i.stringof~";");
    }
    num1=1;
    num2=2;
    num3=3;
    writeln(num1,num2,num3);

... 2) no. This should work for compile time foreach and TypeTuples. There are many examples in source code of Phobos.

The following code fails to compile: import std.typetuple, std.stdio; void main(){ foreach(i; TypeTuple!(1,2,3)){ mixin("int num"~i.stringof~";"); } num1=1; num2=2; num3=3; writeln(num1,num2,num3); }

I am wrong, foreach always has own scope foreach(S; TypeTuple!(string, wstring, dstring)) { import std.conv : to; S a = " a bcd ef gh "; assert(equal(splitter(a), [to!S("a"), to!S("bcd"), to!S("ef"), to!S("gh")])); a = ""; assert(splitter(a).empty); } You can use mixin and format instead: import std.typetuple, std.stdio; void main(){ mixin(format("%(int num%s; %);", [1,2,3])); //<---- num1=1; num2=2; num3=3; writeln(num1,num2,num3); }
Jan 30 2014
prev sibling next sibling parent "Ilya Yaroshenko" <ilyayaroshenko gmail.com> writes:
On Thursday, 30 January 2014 at 17:12:51 UTC, Etienne wrote:
 On 2014-01-30 10:28 AM, Timon Gehr wrote:
 import std.typetuple, std.stdio;
 void main(){
     foreach(i; TypeTuple!(1,2,3)){
         mixin("int num"~i.stringof~";");
     }
     num1=1;
     num2=2;
     num3=3;
     writeln(num1,num2,num3);
 }

This written as a static foreach or declarative foreach, would be awesome if it exposed the scope. For now the only solution is to build a string and mixin the string like this string fct(R)(){ string str; foreach (range ; R) str ~= "int num " ~ range.stringof ~ ";"; return str; } mixin(fct!(TypeTuple!(1,2,3)); writeln(num1,num2, num3); Looks a little less convenient than static foreach ( i; 0..3 ) mixin("int num" ~ i.stringof ~ ";"); writeln(num1,num2, num3);

It works now: mixin(format("%(int num%s; %);", [1,2,3])); //<---- writeln(num1,num2,num3);
Jan 30 2014
prev sibling next sibling parent "Ilya Yaroshenko" <ilyayaroshenko gmail.com> writes:
On Thursday, 30 January 2014 at 17:13:21 UTC, Ilya Yaroshenko 
wrote:
 On Thursday, 30 January 2014 at 15:28:34 UTC, Timon Gehr wrote:
 On 01/30/2014 03:08 PM, Ilya Yaroshenko wrote:
 2) `foreach` creates it's own scope. This won't work:
   foreach(i; TypeTuple!(1,2,3)){
       mixin("int num"~i.stringof~";");
   }
   num1=1;
   num2=2;
   num3=3;
   writeln(num1,num2,num3);

... 2) no. This should work for compile time foreach and TypeTuples. There are many examples in source code of Phobos.

The following code fails to compile: import std.typetuple, std.stdio; void main(){ foreach(i; TypeTuple!(1,2,3)){ mixin("int num"~i.stringof~";"); } num1=1; num2=2; num3=3; writeln(num1,num2,num3); }

I am wrong, foreach always has own scope foreach(S; TypeTuple!(string, wstring, dstring)) { import std.conv : to; S a = " a bcd ef gh "; assert(equal(splitter(a), [to!S("a"), to!S("bcd"), to!S("ef"), to!S("gh")])); a = ""; assert(splitter(a).empty); } You can use mixin and format instead: import std.typetuple, std.stdio; void main(){ mixin(format("%(int num%s; %);", [1,2,3])); //<---- num1=1; num2=2; num3=3; writeln(num1,num2,num3); }

import std.string; =)
Jan 30 2014
prev sibling next sibling parent "Idan Arye" <GenericNPC gmail.com> writes:
On Thursday, 30 January 2014 at 14:08:27 UTC, Ilya Yaroshenko 
wrote:
 1) You can use 
 mixin(format("<Prolog>%(<Begin>%s<End>%)<Epilog>", 
 CompileTimeRange)); See std.format for ranges and 
 std.string.format.

That works for simple cases. Complex cases require that you write a function and calling it with CTFE for creating the mixin string. This can be a problem outside functions, because you are polluting the namespace. It might not be that bad for structs and classes, since you can simply make that helper function private - but for template mixins the helper function must be exposed to the code that uses the mixin. Having complex strings mixing also means that if there is a problem in that string, the compiler will point to the line where the string is mixed in, not the one where the code that introduces the error is concentrated to the string. Having a static foreach will make the string mixins small and simple enough for this to not be a problem. Metaprogramming by building the code strings is not something that should be encouraged(*cough* C macros *cough*), but it's currently required sometimes for complex metaprogramming in D. If we had static foreach we would still need string mixins, but at least we will be mixing in many small simple strings instead of one big complex string, and that means we could add to Phobos some helper template mixins for sanitizing the arguments for those mixins.
Jan 30 2014
prev sibling next sibling parent "Dicebot" <public dicebot.lv> writes:
Andrei has stated at least once that he agrees about usefulness / 
necessity of declaration foreach. It is mostly matter of someone 
doing implementation, same as for many other "hot" discussed 
stuff.
Jan 30 2014
prev sibling next sibling parent "deadalnix" <deadalnix gmail.com> writes:
On Thursday, 30 January 2014 at 11:19:33 UTC, Idan Arye wrote:
 Two problems:

 1) You can't use `foreach` outside functions. That means that 
 you write:
     struct Foo{
         foreach(i;TypeTuple!(1,2,3)){
             mixin("int num"~i.stringof~";");
         }
     }

mixin({ foreach(...) { ... } }())
 2) `foreach` creates it's own scope. This won't work:
     foreach(i; TypeTuple!(1,2,3)){
         mixin("int num"~i.stringof~";");
     }
     num1=1;
     num2=2;
     num3=3;
     writeln(num1,num2,num3);

You got to mixin the whole stuff, not field by field, and you won't have any issue.
Jan 30 2014
prev sibling parent "Idan Arye" <GenericNPC gmail.com> writes:
On Thursday, 30 January 2014 at 23:06:36 UTC, deadalnix wrote:
 On Thursday, 30 January 2014 at 11:19:33 UTC, Idan Arye wrote:
 Two problems:

 1) You can't use `foreach` outside functions. That means that 
 you write:
    struct Foo{
        foreach(i;TypeTuple!(1,2,3)){
            mixin("int num"~i.stringof~";");
        }
    }

mixin({ foreach(...) { ... } }())

Except it doesn't work inside classes and structs - it complains that "function literals cannot be class members". You have to define a named function and pollute the namespace.
 2) `foreach` creates it's own scope. This won't work:
    foreach(i; TypeTuple!(1,2,3)){
        mixin("int num"~i.stringof~";");
    }
    num1=1;
    num2=2;
    num3=3;
    writeln(num1,num2,num3);

You got to mixin the whole stuff, not field by field, and you won't have any issue.

Yes, you can concatenate strings to create code. I've already answered to Ilya about that, so I'm going to copy-paste my answer: " Having complex strings mixing also means that if there is a problem in that string, the compiler will point to the line where the string is mixed in, not the one where the code that introduces the error is concentrated to the string. Having a static foreach will make the string mixins small and simple enough for this to not be a problem. Metaprogramming by building the code strings is not something that should be encouraged(*cough* C macros *cough*), but it's currently required sometimes for complex metaprogramming in D. If we had static foreach we would still need string mixins, but at least we will be mixing in many small simple strings instead of one big complex string, and that means we could add to Phobos some helper template mixins for sanitizing the arguments for those mixins. "
Jan 30 2014