Avoiding some template bloat?
bearophile
bearophileHUGS at lycos.com
Mon Oct 8 15:13:13 PDT 2012
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
More information about the Digitalmars-d
mailing list