I apologize for the title, as I couldn't come up with something succinct and descriptive enough while still being reasonably short. We all know how powerful templates are in C++/D, but they have that damning cost of needing to generate new code for each new template argument. For example:

string format(string fmtStr)(T data)
{
    ...
}

As fmtStr is a compile-time template argument, the compiler will have to generate N nearly-identical format functions for N strings that format is instantiated with. This "template bloat" increases exponentially as more template arguments are added. Take std.exception.assertNotThrown:

void assertNotThrown(T : Throwable = Exception, E)
                    (lazy E expression,
                     string msg = null,

                     //Could be compile-time, but aren't,
                     //because of template bloat worries
                     string file = __FILE__,
                     size_t line = __LINE__)
{
    try
        expression();
    catch(T t)
    {
        immutable message = msg.empty ? t.msg : msg;
        immutable tail = message.empty ? "." : ": " ~ message;
throw new AssertError(format("assertNotThrown failed: %s was thrown%s",
                                     T.stringof,
                                     tail),
                              file,
                              line,
                              t);
    }
}

The arguments file and line are known at compile time, but should not be made template arguments because there is a potential to create N*M copies of assertNotThrown, given N values of __FILE__ and M values of __LINE__.

Adding large numbers of template arguments spuriously can cause an explosion in binary size, especially when templates are very heavily used, such as in most D code. Furthermore, programmers worried about such bloat will undoubtedly take the safe route, and try to move as many arguments as possible from the compile-time parameter list to run-time. This means that generated code will not necessarily be efficient as it should be.

I'd like to ask, however, why should this be? The arguments line and file will only ever be strings and ints. They don't vary in type, only value, and changing those values will not affect how the function works. You could easily substitute out "exception.d" with "stdio.d", and the functionality will be unchanged.

This remains true for values of the same type. It doesn't matter whether you pass 1000, 10_000, or 100_000 as a template argument. The way the function operates is not observably different (note that for this to be true, we have to assume that static if does not exist). This is in contrast to passing a completely different type for T (say, RangeError), which may change how the function works.

I'd like to know, are there any template "optimizations" that DMD currently implements which mitigates such a situation such as with assertNotThrown, where a compile-time argument may vary only in value, and not type? Judging from the fact that this function was changed so that that __FILE__ and __LINE__ are taking as run-time parameters, I guess the answer is no. Is there anything we can do about this?

Reply via email to