Understanding Templates: why can't anybody do it?

Nick Sabalausky a at a.a
Sat Mar 17 13:20:42 PDT 2012


"Entity325" <lonewolf325 at gmail.com> wrote in message 
news:pgzkvphadihglayfulij at forum.dlang.org...
>
> That is to say, nobody understands how Templates work is because, as was 
> the case for me, the sum total of explanation we are given on them in 
> programming class is "These are Templates.  They exist.  They look 
> something like this."
>

I've spent a total of about 6 years in college, always got A's and B's in 
the CS classes, and yet I'm convinced that programming classes are 
completely and utterly useless. Most of the instructors themselves barely 
even know how to code (among many, many other problems). So if you're 
learning programming at college, then I'm not surprised you've gotten the 
impression that nobody understands templates: Around universities, they 
probably don't. (Also, it doesn't help that C++'s templates could be a lot 
better.)

I even had one CS prof, he said that the only language he knew was C. And 
yet, after seeing his feedback on my code, it became abundantly clear to me 
that he didn't even understand how strings worked (ie: C's famous 
null-terminated strings) *in the ONE language he knew*.

Here's a little templates primer, I hope it helps:

A template is just clean way of generating code instead of manually 
copy-pasting a bunch of times (which would end up being a maintenance 
nightmare).

Suppose you have this:

    int add(int a, int b)
    {
        return a + b;
    }

Call it, of course, like this:

    add(7, 42);

And then you want another version that does it with doubles:

    double add(double a, double b)
    {
        return a + b;
    }

Called, of course, like this:

    add(3.14, 5.73);

Now you've got two copies that are exactly the same thing, just with one 
little thing changed. If you need top modify one, you'll have to remember to 
modify the other too. And god help you if it's a really big function and you 
accidentally make a mistake copying it. It just gets to be a big problem. It 
violates what we call DRY: "Don't Repeat Yourself".

Let's step back into first grade for a minute: Have you ever drawn or 
painted with stencils? You make one design, once, by cutting it out of 
paper. Then you can easily draw and re-draw the same design with different 
colors: Just place the stencil on a new piece of paper, choose a color, 
color it, lift the stencil, and there's another copy of your design, but in 
whatever different color you wanted.

Templates are stencils for code. Heck, even outside of code this is true 
too. "template" is just another word for "stencil".

So here's how you make an "add() function" stencil. Just "punch out" what 
you want to change, by making it a template parameter:

    // Make a "stencil"
    template myTemplate(T)
    {
        // This is what's in the "stencil"
        T add(T a, T b)
        {
            return a + b;
        }
    }

Notice how the add() function is exactly the same as before, but the "int"s 
were changed to "T" (for "Type").

Let's stamp down some "add()" prints:

    // The dot in between is because "add" is *inside* the template 
"myTemplate"
    myTemplate!(int).add(7, 42);
    myTemplate!(double).add(3.14, 5.73);

Notice how that's exactly the same as before, but I have that 
"myTemplate!(int)", and another with "float". The compiler turns that into:

    int add(int a, int b)
    {
        return a + b;
    }
    add(7, 42);

    double add(double a, double b)
    {
        return a + b;
    }
    add(3.14, 5.73);

Suppose now we also want to be able to use add() for ulong and BigInt. 
Instead of manually copying the function and changing the type, we can just 
let the *compiler* copy the function and change the type:

    myTemplate!(ulong).add(7, 42);
    myTemplate!(BigInt).add(BigInt(7), BigInt(42));

The compiler, of course, automatically generates:

    ulong add(ulong a, ulong b)
    {
        return a + b;
    }

    BigInt add(BigInt a, BigInt b)
    {
        return a + b;
    }

And then calls them:

    add(7, 42);
    add(BigInt(7), BigInt(42));

You can also add more stuff to the template:

    // A bigger "stencil"
    template myTemplate(T)
    {
        T add(T a, T b)
        {
            return a + b;
        }

        T mult(T a, T b)
        {
            return a * b;
        }
    }

So now the compiler will turn this:

    myTemplate!(int)
    myTemplate!(double)

Into this:

    int add(int a, int b)
    {
        return a + b;
    }

    int mult(int a, int b)
    {
        return a * b;
    }

    double add(double a, double b)
    {
        return a + b;
    }

    double mult(double a, double b)
    {
        return a * b;
    }

And you can use them like this:

    myTemplate!(int).add(7, 42);
    myTemplate!(double).add(3.14, 5.73);

    myTemplate!(int).mult(7, 42);
    myTemplate!(double).mult(3.14, 5.73);

You can put other things inside the template, too, like structs and classes, 
or even variables:

    template anotherTemplate(T, int initialValue)
    {
        struct Foo
        {
            T value;
            int someInt = initialValue;
        }

        T[] myArray;
    }

    // Use the array:
    anotherTemplate!(string, 5).myArray = ["abc", "def", "g"];

    // Declare a variable "myFoo" of type "Foo":
    //     Foo's "value" should be a string
    //     And Foo's "someInt" should start out as 5
    anotherTemplate!(string, 5).Foo myFoo;
    if(myFoo.someInt == 5)
        myFoo.value = "Hello";

D has some convenient tricks though:

Using that "myTemplate" and "anotherTemplate" all over is a bit of a bother. 
Why should we have to? D has a shortcut for making and using templates:

    T add(T)(T a, T b)
    {
        return a + b;
    }

That's nothing more than a convenient shortcut for:

    template add(T)
    {
        T add(T a, T b)
        {
            return a + b;
        }
    }

Or the struct:

    struct Foo(T, int initialValue)
    {
        T value;
        int someInt = initialValue;
    }

Which is a convenient shortcut for:

    template Foo(T, int initialValue)
    {
        struct Foo
        {
            T value;
            int someInt = initialValue;
        }
    }

The same trick doesn't work for variables like myArray though, you'll have 
to manually write them as:

    template myArray(T)
    {
        T[] myArray;
    }

Here's another nice trick: Since the name of the template and the "thing" 
inside the template is the same, you don't have to repeat them. All you have 
to write is:

    add!int(7, 42);
    add!double(3.14, 5.73);
    add!BigInt(BigInt(7), BigInt(42));

    mult!int(7, 42);
    mult!double(3.14, 5.73);
    mult!BigInt(BigInt(7), BigInt(42));

    myArray!string = ["abc", "def", "g"];
    myArray!int = [1, 2, 3];
    myArray!double = [1.5, 2.70, 3.14];

    Foo!(string, 5) myFoo;
    if(myFoo.someInt == 5)
        myFoo.value = "Hello";

    Foo!(float, 1) bar;
    if(bar.someInt == 1)
        bar.value = 3.14;

And there's yet one more convenience: A special thing D has called IFTI: 
Implicit Function Template Instantiation. Yes, it sounds very intimidating, 
but it's really very easy. D lets you call the functions above like this:

    add(7, 42);
    add(3.14, 5.73);
    add(BigInt(7), BigInt(42));

    mult(7, 42);
    mult(3.14, 5.73);
    mult(BigInt(7), BigInt(42));

Look ma! No types!

D already knows that 7 and 43 are int, so it automatically uses the 
"add!int" version.

D already knows that 3.14 and 5.73 are double, so it automatically uses the 
"add!double" version.

D already knows that BigInt(7) and BigInt(42) are BigInt, so it 
automatically uses the "add!BigInt" version.

So, we've started with this copy-paste mess:

    int add(int a, int b)
    {
        return a + b;
    }

    double add(double a, double b)
    {
        return a + b;
    }

    add(7, 42);
    add(3.14, 5.73);
    add(BigInt(7), BigInt(42)); // ERROR! You didn't write a BigInt version!

And turned it into this:

    T add(T)(T a, T b)
    {
        return a + b;
    }

    add(7, 42);
    add(3.14, 5.73);
    add(BigInt(7), BigInt(42));
    add( /+ anything else! +/ );

Which automatically stamps out any "add()" function you need, when you need 
it. And we can do the same for structs, classes and variables.




More information about the Digitalmars-d mailing list