expanding variadic into format

Stanislav Blinov stanislav.blinov at gmail.com
Wed Oct 31 12:54:52 UTC 2018


On Wednesday, 31 October 2018 at 12:13:57 UTC, Codifies wrote:
> On Wednesday, 31 October 2018 at 12:09:04 UTC, Stanislav Blinov 
> wrote:

>> ```
>> void printValue(Args...)(Font fnt, float x, float y, string 
>> frmt, auto ref Args args) {
>>     // ...
>>     import std.functional : forward;
>>     string message = format(frmt, forward!args);
>>     // ...
>> }
>> ```
>
> thats fantastic thanks so much, can you explain a little more 
> about whats going on here ?

As rikki already explained, std.format is a variadic template, 
which gets expanded into argument list at compile time. That's 
why it can't be used with C-syle variadics: when you passed 
_arguments, the expansion treated that as a single argument 
instead of a tuple.
Therefore, to forward arguments to std.format, your `printValue` 
must also be a variadic.

There are, broadly speaking, two ways to pass arguments to 
functions: by value and by reference.

When you make a template like this:

void foo(T)(T value) { /* ... */ }

it will take the argument by value, making copies when necessary:

struct S { /* ... */ }

S s;
foo(S.init); // calls foo without copying, argument is 
constructed directly
foo(s); // copies `s` and passes that copy to `foo`

If that template is defined like this:

void foo(T)(ref T value) { /* ... */ }

then it will *only* take argument by reference:

foo(S.init); // error, 'ref' cannot bind to rvalue
foo(s); // no copy is made, `foo` takes `s` by reference

There are more subtleties, especially when taking `const ref` 
arguments, but I won't get into those.

There's a special syntax for template functions: `auto ref` 
arguments. Those are deduced to be by-value or by-reference at 
compile time (see
https://dlang.org/spec/template.html#auto-ref-parameters):

void foo(T)(auto ref T value) { /* ... */ }

foo(S.init); // works, compiles as foo(S);
foo(s); // works, compiles as foo(ref S);

But, because inside of function definition all arguments are 
lvalues, you lose this additional information if you pass them to 
another function directly. To preserve that information, there's 
a `forward` template in std.functional. D doesn't have rvalue 
references, so that template will still copy the bits of 
non-`ref` arguments, but it will not call postblits, etc (it 
`move`s them using std.algorithm.mutation.move).

So, there are two possible ways to implement your print:

// Take all Args by value, i.e. copy everything first time
void printValue(Args...)(Font fnt, float x, float y, string frmt, 
Args args) {
     // make copies of every argument in `args` (again) and pass 
those to `format`
     auto message = format(frmt, args);
}

or

// Infer whether each argument is an lvalue or not
void printValue(Args...)(Font fnt, float x, float y, string frmt, 
auto ref Args args) {
     import std.functional : forward;
     // preserve lvalue/rvalue
     string message = format(frmt, forward!args);
}

Both allow you to accomplish your goal, but the second one only 
copies the argument bits when necessary.

Getting into finer implementation nuances, conceptually this 
allows a function to even take and pass around non-copyable types 
as arguments. Sadly, this is not widely adopted by Phobos, which 
likes to make unnecessary copies. I.e. the `format` function 
itself takes Args by value, even though it probably should take 
advantage of this specific language feature. But at least calling 
it via `forward`, you only make necessary copies once, instead of 
twice.



More information about the Digitalmars-d-learn mailing list