www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - How to properly use variadic templates (functions)?

reply rempas <rempas tutanota.com> writes:
I'm trying to implement "printf" and I'm getting an error. The 
smallest possible code to demonstrate the error is:

```
import core.stdc.stdio;

void print(T...)(string prompt, T args) {
   // Suppose all 'args' are of type "string" for this example
   ulong carg = 0;
   for (ulong i = 0; i < args[carg].length; i++) {
     putc(args[carg][i], stdout);
   }
}

void main() {
   print("Prompt (ignored)", "Hello", " world!\n");
}
```

The error output I'm getting is:

```
test.d(6): Error: variable `carg` cannot be read at compile time
test.d(7): Error: variable `carg` cannot be read at compile time
test.d(12): Error: template instance `test.print!(string, 
string)` error instantiating
```

There is another error in my original code. Let's see the 
following line:

`u64 str_len = strlen(args[carg]); //u64 = ulong`

Now this should also give me the error that the variable "carg" 
cannot be read at compile time (which it probably does as it 
gives me an error when trying to use "str_len" inside a "for 
loop") but instead it gives me the following error:

```
Error: function `core.stdc.string.strlen(scope const(char*) s)` 
is not callable using argument types `(string)`
        cannot pass argument `_param_1` of type `string` to 
parameter `scope const(char*) s`
```

Any ideas?
Dec 20 2021
next sibling parent reply Adam D Ruppe <destructionator gmail.com> writes:
On Monday, 20 December 2021 at 21:26:45 UTC, rempas wrote:
   // Suppose all 'args' are of type "string" for this example
still use foreach(arg; args) and skip that index variable.
Dec 20 2021
parent rempas <rempas tutanota.com> writes:
On Monday, 20 December 2021 at 21:49:59 UTC, Adam D Ruppe wrote:
 still use

 foreach(arg; args)

 and skip that index variable.
I know I can use foreach ("static foreach" more specifically) but I need to be able to get the index to choose a specific argument because I want to do formatting (that's why I said "printf")
Dec 20 2021
prev sibling parent reply russhy <russhy_s gmail.com> writes:
Here how i'd do, but i'm not sure how to keep track of the index 
of the arguments, i forgot..

```D
import core.stdc.stdio: putc, stdout;

void print(T...)(string prompt, T args)
{
     foreach (a; args)
     {
         alias A = typeof(a);

         static if (is(A : string))
         {
             for (int j = 0; j < a.length; j++)
             {
                 putc(a[j], stdout);
             }
         }
         else
         {
             // handle your other types
             print("", A.stringof);
         }
     }

}

void main()
{
     print("Prompt (ignored)", "Hello", " world!\n", 123);
}
```
Dec 20 2021
parent reply rempas <rempas tutanota.com> writes:
On Monday, 20 December 2021 at 22:02:02 UTC, russhy wrote:
 Here how i'd do, but i'm not sure how to keep track of the 
 index of the arguments, i forgot..

 ```D
 import core.stdc.stdio: putc, stdout;

 void print(T...)(string prompt, T args)
 {
     foreach (a; args)
     {
         alias A = typeof(a);

         static if (is(A : string))
         {
             for (int j = 0; j < a.length; j++)
             {
                 putc(a[j], stdout);
             }
         }
         else
         {
             // handle your other types
             print("", A.stringof);
         }
     }

 }

 void main()
 {
     print("Prompt (ignored)", "Hello", " world!\n", 123);
 }
 ```
This will not do for me because I want to do formatted output and I need the index. But thanks a lot for tying to help!
Dec 20 2021
parent reply Anonymouse <zorael gmail.com> writes:
On Tuesday, 21 December 2021 at 06:44:36 UTC, rempas wrote:
 This will not do for me because I want to do formatted output 
 and I need the index. But thanks a lot for tying to help!
I'm not certain I understand, but won't `foreach (i, a; args) { /* ... */ }` in his example do that? As in, if you necessarily must index `args` instead of using a foreach variable, ```d import core.stdc.stdio : putc, stdout; void print(T...)(string prompt, T args) { foreach (i, a; args) { alias A = typeof(args[i]); static if (is(A : string)) { for (int j = 0; j < args[i].length; j++) { putc(args[i][j], stdout); } } else { // handle your other types print("", A.stringof); } } } void main() { print("Prompt (ignored)", "Hello", " world!\n", 123); } ```
Dec 21 2021
parent reply rempas <rempas tutanota.com> writes:
On Tuesday, 21 December 2021 at 08:11:39 UTC, Anonymouse wrote:
 I'm not certain I understand, but won't `foreach (i, a; args) { 
 /* ... */ }` in his example do that?

 As in, if you necessarily must index `args` instead of using a 
 foreach variable,

 ```d
 import core.stdc.stdio : putc, stdout;

 void print(T...)(string prompt, T args)
 {
     foreach (i, a; args)
     {
         alias A = typeof(args[i]);

         static if (is(A : string))
         {
             for (int j = 0; j < args[i].length; j++)
             {
                 putc(args[i][j], stdout);
             }
         }
         else
         {
             // handle your other types
             print("", A.stringof);
         }
     }
 }

 void main()
 {
     print("Prompt (ignored)", "Hello", " world!\n", 123);
 }
 ```
No it will not. I will try to explain it the best way I can. When I say I want to index args, I mean that I want to index and choose which argument to use rather than use them continuously one after another inside a `foreach`. For example check the following call: `print("Hello %s!%c", "world", '\n');` In that case I want to first print print from "H" up to (but not include) "%s". Then I want to print the first argument. After that, I want to print the '!' character and then I want to print the second argument. So I need a way to keep track which argument was the last one I printed and manually choose which argument to use. So `foreach` will not do in that case because I don't want to continuously use the arguments one after another. Is is more clear now? "writef" exists in phobos so I'm pretty sure that there is a way to do that.
Dec 21 2021
parent reply vit <vit vit.vit> writes:
On Tuesday, 21 December 2021 at 08:26:17 UTC, rempas wrote:
 On Tuesday, 21 December 2021 at 08:11:39 UTC, Anonymouse wrote:
 I'm not certain I understand, but won't `foreach (i, a; args) 
 { /* ... */ }` in his example do that?

 As in, if you necessarily must index `args` instead of using a 
 foreach variable,

 ```d
 import core.stdc.stdio : putc, stdout;

 void print(T...)(string prompt, T args)
 {
     foreach (i, a; args)
     {
         alias A = typeof(args[i]);

         static if (is(A : string))
         {
             for (int j = 0; j < args[i].length; j++)
             {
                 putc(args[i][j], stdout);
             }
         }
         else
         {
             // handle your other types
             print("", A.stringof);
         }
     }
 }

 void main()
 {
     print("Prompt (ignored)", "Hello", " world!\n", 123);
 }
 ```
No it will not. I will try to explain it the best way I can. When I say I want to index args, I mean that I want to index and choose which argument to use rather than use them continuously one after another inside a `foreach`. For example check the following call: `print("Hello %s!%c", "world", '\n');` In that case I want to first print print from "H" up to (but not include) "%s". Then I want to print the first argument. After that, I want to print the '!' character and then I want to print the second argument. So I need a way to keep track which argument was the last one I printed and manually choose which argument to use. So `foreach` will not do in that case because I don't want to continuously use the arguments one after another. Is is more clear now? "writef" exists in phobos so I'm pretty sure that there is a way to do that.
You can use switch + static foreach: ```d import std.stdio; //this print args in reverse order: void print(T...)(string prompt, T args) { void print_arg(size_t index){ switch(index){ static foreach(i, a; args){ case i: // handle your other types write(a); return; } default: assert(0, "no impl"); } } write(prompt); size_t len = args.length; while(len --> 0) print_arg(len); } void main(){ print("Prompt (ignored): ", "Hello", " world!\n", 123); } ```
Dec 21 2021
parent reply rempas <rempas tutanota.com> writes:
On Tuesday, 21 December 2021 at 08:42:35 UTC, vit wrote:
 You can use switch + static foreach:

 ```d
 import std.stdio;

     //this print args in reverse order:
     void print(T...)(string prompt, T args)
     {
         void print_arg(size_t index){
             switch(index){
                 static foreach(i, a; args){
                 	case i:
                         // handle your other types
                         write(a);
                     	return;	
                 }
                 default:
                 	assert(0, "no impl");
             }
         }

         write(prompt);
         size_t len = args.length;
         while(len --> 0)
             print_arg(len);
     }

     void main(){
         print("Prompt (ignored): ", "Hello", " world!\n", 123);
     }

 ```
Cool! That's probably what I wanted to do! It seems that when looping inside a "static foreach" and taking the index, then I can compare it with a value that is not calculated at compile time. This way I can also check for the type of the variables and do my original plan which will make the function even better! Thanks a lot for the help dude!!!
Dec 21 2021
next sibling parent reply russhy <russhy_s gmail.com> writes:
Please keep us updated, that'll be interesting to see how a pure 
D printf would look like!
Dec 21 2021
next sibling parent rempas <rempas tutanota.com> writes:
On Tuesday, 21 December 2021 at 15:42:59 UTC, russhy wrote:
 Please keep us updated, that'll be interesting to see how a 
 pure D printf would look like!
Glad someone is interested! I'm actually planning to make a whole library ;) Check my [thread](https://forum.dlang.org/thread/frjbgaymuxjqperiskqp forum.dlang.org) were I'm asking about your opinion on formatting and what is my current approach.
Dec 21 2021
prev sibling parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Tuesday, 21 December 2021 at 15:42:59 UTC, russhy wrote:
 Please keep us updated, that'll be interesting to see how a 
 pure D printf would look like!
It already exists, it's called std.format.write.formattedWrite, in terms of which things like std.stdio.writef are implemented.
Dec 21 2021
parent reply russhy <russhy_s gmail.com> writes:
On Tuesday, 21 December 2021 at 18:51:38 UTC, Stanislav Blinov 
wrote:
 On Tuesday, 21 December 2021 at 15:42:59 UTC, russhy wrote:
 Please keep us updated, that'll be interesting to see how a 
 pure D printf would look like!
It already exists, it's called std.format.write.formattedWrite, in terms of which things like std.stdio.writef are implemented.
I took a look and to be honest, it's the same story as everything in the STD, they try to do everything at the same time, so they up end calling each other, you end up lost in multiple 8k LOC modules, not understanding what the function is doing anymore, it's a rotten place Plus they are not nogc OP's attempt is clean, you know what's up, you can read the code, free of dependencies (for now), and hopefully nogc i want to encourage more code like that
Dec 21 2021
parent reply rempas <rempas tutanota.com> writes:
On Tuesday, 21 December 2021 at 22:50:57 UTC, russhy wrote:
 I took a look and to be honest, it's the same story as 
 everything in the STD, they try to do everything at the same 
 time, so they up end calling each other, you end up lost in 
 multiple 8k LOC modules, not understanding what the function is 
 doing anymore, it's a rotten place
Finally someone said it!!! No offense to the Phobos developers but the library is a huge mess in runtime/compile performance and its API is not always clean and consistent making the library annoying to use some times. Hell, even libc is not very good as it is very bare bones and believe it or not, we could achieve better performance for most staff from libc. We need a new system library that won't depend on anything ASAP! No garbage collector, no exceptions, no complicated struct types etc. Just a simple, clean, consistent, really fast and easy to use library!
 Plus they are not nogc
Yeah, Seriously D's developers and user really underestimate the fact that the biggest percent of people not using D are doing so because of the Garbage Collector. D's community makes it like it's not much of a big deal but it actually is.
 OP's attempt is clean, you know what's up, you can read the 
 code, free of dependencies (for now), and hopefully nogc


 i want to encourage more code like that
Thanks! Code readability and a clean and easy to use API is the plan for the library. I want people to enjoy using the library while they will have the best possible runtime and compile performance at the same time. I am a novice even to the easier high level programming and so I will not a lot of help. But this will not stop me and hopefully we can make something great together!
Dec 22 2021
parent eugene <dee0xeed gmail.com> writes:
sorry for intervening... :)

On Wednesday, 22 December 2021 at 08:17:03 UTC, rempas wrote:
 No garbage collector, no exceptions
GOLDEN WORDS!!!
 Yeah, Seriously D's developers and user really underestimate 
 the fact that the biggest percent of people not using D are 
 doing so because of the Garbage Collector. D's community makes 
 it like it's not much of a big deal but it actually is.
Exactly!
Dec 22 2021
prev sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 12/21/21 4:28 AM, rempas wrote:
 On Tuesday, 21 December 2021 at 08:42:35 UTC, vit wrote:
 You can use switch + static foreach:

 ```d
 import std.stdio;

     //this print args in reverse order:
     void print(T...)(string prompt, T args)
     {
         void print_arg(size_t index){
             switch(index){
                 static foreach(i, a; args){
                     case i:
                         // handle your other types
                         write(a);
                         return;
                 }
                 default:
                     assert(0, "no impl");
             }
         }

         write(prompt);
         size_t len = args.length;
         while(len --> 0)
             print_arg(len);
     }

     void main(){
         print("Prompt (ignored): ", "Hello", " world!\n", 123);
     }

 ```
Cool! That's probably what I wanted to do! It seems that when looping inside a "static foreach" and taking the index, then I can compare it with a value that is not calculated at compile time. This way I can also check for the type of the variables and do my original plan which will make the function even better! Thanks a lot for the help dude!!!
The reason your original isn't working is that indexing a list of differently-typed things cannot be done using a runtime index. I'd say that an inner function + static foreach + switch is the best way to convert from runtime to compile-time values. And in general, I would say having function templates that handle each arg type are usually conducive to clean and easy code. But in the case you are suggesting, as long as you don't need to iterate them out of order, what you can do is foreach the args tuple, and do something like: ```d foreach(arg; args) { writeUpToNextDelimeter(prompt); // updates prompt to point at next delimiter processArg(arg, prompt); } ``` -Steve
Dec 21 2021
parent rempas <rempas tutanota.com> writes:
On Tuesday, 21 December 2021 at 17:33:09 UTC, Steven 
Schveighoffer wrote:
 The reason your original isn't working is that indexing a list 
 of differently-typed things cannot be done using a runtime 
 index.

 I'd say that an inner function + static foreach + switch is the 
 best way to convert from runtime to compile-time values. And in 
 general, I would say having function templates that handle each 
 arg type are usually conducive to clean and easy code.

 But in the case you are suggesting, as long as you don't need 
 to iterate them out of order, what you can do is foreach the 
 args tuple, and do something like:

 ```d
 foreach(arg; args)
 {
    writeUpToNextDelimeter(prompt); // updates prompt to point 
 at next delimiter
    processArg(arg, prompt);
 }
 ```

 -Steve
Got that! Thanks a lot Steve!
Dec 21 2021