Maybe someone someday will want to implement two Phobos printing functions usable like this:

writecfln!"%d %s"(10, "hello");
writecf!"%d %s"(10, "hello");

They accept a format string as template argument (format strings are often compile-time constants and sometimes they are computable on the fly at compile-time), verifies at compile time (calling another CTFE function in its template constraint) that the format string matches with the type tuple of the arguments, and then just call writefln()/writef() with the format string and arguments.

This variable-length-templated function avoids format string errors at run-time. This in my opinion means taking advantage of the static typing of D to avoid some bugs. In my opinion it's silly to use a language with static typing, unlike Python, and then _not_ use such static knowledge where it's usable and useful.

Using "%s" everywhere in writefln/writef is an option, but it's not always usable, like when you want to use the advanced new formatting syntax for arrays.

writecfln() and writecf() just call writefln/writef, so they are very light, in the binary they just call another function. But if there are many different format strings you get many similar useless little functions in the binary (maybe inlined).

Is it possible for the D compiler to avoid the (small amount of?) template bloat caused by writecfln/writecf? Is it useful to introduce some kind of annotation to tell the compiler to avoid/remove any bloat for similar functions turning them essentially into just compile-time tests for writefln/writef? Or is it enough to rely on the inliner of the compiler?

Beside writecfln()/writecf() I have other use cases for such ideas. When I write certain kind of matrix code that use fixed-sized 2D matrices, they enforce the correct sizes at compile-time, but then they just call functions that use run-time sized arrays, to avoid template bloat (if necessary slicing the fixed sized matrix into a dynamic array of dynamic arrays). In this case too I'd like to minimize or remove template bloat and just have compile-time tests on data that is then managed with run-time sizes (so it's not actually templated on the size of the matrix).

Here you see a little example, regarding matrix multiplication (here it doesn't call another function with run-time sizes, so this is really templated):


template TMMul(M1, M2) { // helper
alias Unqual!(typeof(M1[0][0]))[M2[0].length][M1.length] TMMul;
}

void matrixMul(T, T2, size_t k, size_t m, size_t n)
              (in ref T[m][k] A, in ref T[n][m] B,
               ref T2[n][k] result) pure nothrow
if (is(T2 == Unqual!T)) {
    T2[m] aux;
    foreach (j; 0 .. n) {
        foreach (k, row; B)
            aux[k] = row[j];
        foreach (i, ai; A)
            result[i][j] = dotProduct(ai, aux);
    }
}


A third use case for such need is a poor's man "homemade" management of integer-indexed dependent types. In such cases you use the compile-time known sizes and values to enforce compile-time sanity constraints, but then you use normal run-time not-templated functions to avoid useless template bloat. This means the compile-time values are meant to be used just by the type system to verify things, but the functions are not actually templated. Similar ghost types and other types that vanish are commonly used in functional languages, like OCaML. Avoiding any template bloat when you do such things is kind of needed, unless you want to produce large binaries that do little at run time.

Bye,
bearophile

Reply via email to