On Monday, 8 April 2019 at 12:23:28 UTC, Ron Tarrant wrote:
I'm digging into templates in an attempt to understand the
signals-n-slots replacement for the observer pattern, but I've
got a question I can't seem to find an answer for and an
example for which I'm unable to solve the error.
First, the question...
Your confusion arises in your understanding of meta programming
and templates. Templates are compile time expressions that use
parameters. They are exactly analogous to parameters in typical
programming but allow one to pass around CT information that
doesn't exist at run time.
It is not the statement itself that is a template but the fact
that it depends on a "template", which is a sort of abstract
variable.
class C(T)
template C(T)
struct(T)
interface(T)
void foo(T)()
etc...
are all things that depend on a template parameter T. One can say
that they are all "templated" or are "templates".
One can also say they are parameterized(meaning they take a
parameter, but the parameters are CT "objects".
Essentially template is the most general and the rest are just
syntactic sugar.
e.g.,
template CC(T)
{
class C
{
T t;
}
}
template CC is a "template"(just like a cookie cutter but that
can configure itself depending on what T is, as this example
shows that we can create a class with different type of t.
D has all kinda of stuff like this that help reduce the bloat of
templates. Sometimes you must use one method over another for
certain effects but usually you can pick and choose.
The idea is not to think of meta programming as something
different than normal programming. It is still programming and
there is enough of an overlap that it benefits one to realize
they are the same thing but are at different levels in the type
system. Meta programming works on the type hierarchy while
"runtime programming" works in the type hierarchy. Meta
programming is a super set of runtime programming.
The way I think of meta programming is that of coding runtime
code. Run time code codes binary code. If I create a statement
like
writeln("test");
I know that the code gets translated in to a call instruction,
pointers are used, "test" exists in some location in memory,
etc... it all is machine code though when I compile.
When I do something like this
foo!int();
I know that the first part of foo!int is "meta code" and it first
gets translated in to runtime code and then that code gets
translated in to machine code.
For example
void foo(T)(T s) { writeln(s); }
in this case, depending on what T is at compile time(which I get
to decide in some way and so I know exactly what it is at compile
time, in theory), foo takes on different versions.
if I call foo(3) then it is writeln(3) and T is an int(by
deduction in the compiler) and the compiler can even optimize out
certain things here because it also knows that 3 is known.(this
is CTFE) There is nothing to optimize in that example though.
if I call foo(readln()) then the the compiler cannot optimize out
but this code get translated in to writeln(readlin()) but T is a
string(since readln returns a string and the compiler can deduce
it).
But in general foo acts as all these possibilities(and all the
possibilities are known by the compiler because they have to be
known to compile and use).
So, what does templates do? They combine very similar code in to
meta blocks that can then be easily used. They allow constructing
very complex meta code such as
void foo(T)(T s) { static if (is(T == string)) writeln(s); }
and now foo is much more complicated because it has two
completely different behaviors depending on what T is. You can't
get that with run time code without a lot of work and it will
never be efficient. But here the compiler will eventually know
what T is and it can then choose the correct path at compile
time. The if will disappear and so no extra overhead exists at
runtime.
So, templates are very powerful things(the above doesn't even
dent their power) The idea to bear in mind is that anything in D
is a template if it takes a template parameter. Think of a
template as a drawing template used to sketch something. It isn't
the final result but it shapes the final result. It is more like
a blueprint or a program in and of itself.
I think the hard part for many is that they can't balance the
meta programming part with the run time part. This is very easy
though if one always just keeps track of which side one is
programming in and not to mix them up(mentally).
The meta programming part will always be obvious, it will depend
on template parameters and use meta programming constructs. There
are sometimes overlap between the two levels but it because
natural once one gets enough of an understanding.
The hardest part about D is that it has so much meta programming
stuff and there are sometimes bugs and special cases to do
things, but the grammatical design of it's type system is very
nice and sane compared to most other procedural programming
languages.
Always keep in mind that the templating system is about creating
"generic" code. source code that generates source code(D could
spit out a non-templated source code version if it was designed
to) which then generates machine code.
Each higher level allows one to organize levels below it and
create generic solutions to problems.
Ideally we would have even higher levels but programmers haven't
evolved to this point yet(maybe some functional languages have).