template: a pattern or mold that regulates the shape or appearance of a construction or idea. See also lovemap.

Dictionary of Sexology Project: Main Index

In C++ templates are used to write generic code. There are two sorts of template: function templates and class templates.

Some applications of C++ templates are :-

"Templates" are a feature of C++ which provide limited support for generic programming, even though it's an early-binding language. You might also consider templates a more powerful and convenient replacement for macros. In fact, we've just said the same thing in two different ways. "Let's have a look under the hood", as Ross Perot used to say before the lawn gnomes dragged him screaming and wiggling down to Hell and fed his tremulous midget heart to the Whipper-Choppers.

We'll start with a very simple example, min(). min() takes two arguments and returns the lesser of the two. This is how you might write it as an ordinary function:

    int min( int a, int b )
    {
        return ( a < b ) ? a : b;
    }

The problem there is that it won't work with any type other than int. You could write a double dblmin( double a, double b ) and a dozen others, but you'd spend a lot of time copying and pasting code, and the only thing that would change each time would be the types. The body of the function would never change. The C/C++ macro facility is a much better way to do it:

    [#define] min( a, b )   ( ( (a) < (b) ) ? (a) : (b) )

You'd "call" both the function and the macro in exactly the same way, but while the function would be an ordinary function call, the macro would be something else entirely: It would be expanded before the code is compiled. If you use our min() macro and then instruct your compiler to give you preprocessor output instead of compiling, you'll see something like this:

/* before */
    int a = min( foo(), 5 );

/* after */
    int a = ( ( (foo()) < (5) ) ? (foo()) : (5) );

The preprocessor just replaces a with whatever you give it for the first parameter, and b with whatever you give it for the second parameter. Macros just perform a search and replace on the source code before the compiler sees it. Note that a is a function call, and it gets called twice. That might be a very bad thing. Worse yet, it might only be a moderately bad thing (a harmful recessive, so to speak). This is only one of the reasons why macros are considered harmful by many programmers. Another is that the "invisible" changes in the source make life difficult when you use interactive debuggers or look at the errors and warnings generated by your compiler. The code that's being compiled bears an oftimes arbitrary resemblance to what's in the source file. The only great thing about macros is that they can let you reuse logic without investing in a function call, but as we've seen, you might be squandering hard-earned cycles on other redundant calls. Besides, C++ supports inline functions which do inline code, but put the arguments in temporaries. You can still use macros if your chest is that hairy. Once in a while, they're the Right Thing.

The purpose of templates is to let us have our cake and eat it. Here's a third min() implementation, the way George Jetson would write it. It's in C++ -- The Language of the Future!

    template <class T>
    inline T min( T a, T b )
    {
        //  GCC prefers that you cast b to T1 here
        return ( a < b ) ? a : b;
    }

Templates let you parameterize not data, but data types. What's happening here is that we're defining a little "function factory" which generates code at compile time: It takes one type parameter, T, and it generates a function which expects two (in fact, you can use regular ordinary types for template parameters also, but it's not enough to be generally useful for currying functions or anything like that). Whenever the compiler sees min() in use, it uses this template to generate code for min() with the type parameter -- T in our example -- replaced by the types of arguments passed to the function. The template is a "pattern" used to generate C++ source code. Source code is just text. If you throw in a call to a.foo(), any class with an appropriate member function named foo() will be acceptable to the template. Inheritance is totally irrelevant because we're not fitting different "plugs" into the same "socket". Instead, we're generating new "sockets" as needed.

Here it is in action:

    std::string foo = "foo", bar = "bar";

    //  std::string has an operator<() member function, 
    //  so why not?
    std::string low = min( foo, bar );

    //  Useless, schmuseless!
    int n = min( argc, fileno( stdin ) );

All's well, until ambiguity rears its ugly head. You could easily call min( 6, 3.65 ). Should the compiler assume that T is double1, or int? It can't make that call, so it throws up its hands and says "template parameter 'T' is ambiguous" or some such thing. You can work around that by explicitly providing T:

    n = min<double>( 6, 3.65 );

That's an annoyance. There's a better way:

    template <class T1, class T2>
    inline T1 min( T1 a, T2 b )
    {
        return ( a < b ) ? a : b;
    }

There. We're no longer requiring that the two types be the same. We never had any good reason to do that in the first place. They are whatever they are. The return type nags at my conscience, but it seems to make sense for it to be the lefternmost of the two arguments. A macro would handle that more tactfully: It would return exactly the value that it returns. With macros, there shall be no coercion in types. What happens if we try wildly incompatible types, like a string class and an int? The compiler will generate code for the function if it possibly can. If it can't, it won't, and it'll scold you. If that string class is on the left and it has an operator<() overload for int, you'll be fine.

Let's move on to a more advanced example: A class template.

    template <class T, int SIZE = 128>
    class stack {
    public:
        //  Constructor
        stack()  { _end = _data = new T[ SIZE ]; }

        //  Virtual destructors make subclassing safe.
        virtual ~stack()  { delete[] _data; }

        //  Expose size with an anonymous enum, since that's 
        //  the only way to do const member data in C++
        enum { size = SIZE };

        //  _end points one past the top item
        int count() { return _end - _data; }

        T & pop()   { return *--_end; }

        T & top()   { return *(_end - 1); }

        void push( const T & newdata )  { *_end++ = newdata; }

    private:
        T * _data;
        T * _end;       //  _end points one past the top item
    };

This works much the same as the function we saw above. We're using references and const references now instead of just throwing T instances around, but a real implementation of min() would have done the same: The objects in question might be large, or copying them might be unwise for other reasons, so it pays to play it safe. The SIZE parameter has a default parameter: If you don't give it a size, it will use 128. Everything else is perfectly ordinary C++ code. Here's how you'd use it:

    stack<int, 16>      st1;
    stack<std::string>  st2;

    st2.push( "foo" );
    st1.push( 456 );

That's very useful: int and std::string are very, very different kinds of things, but we can use the exact same code to store either one in a stack. Furthermore, we're not doing it with unsafe typecasts or ignoring the type completely, the way a macro would. It's typesafe, which in C++ is a big deal.

Here's the catch: It's typesafe, and in C++, that's a big deal. We're working with a relatively low-level language. Each of those objects on that stack has a size. If you want your stack<std::string> to work with subclasses of std::string, you'll need to store pointers instead of object instances, and life (a.k.a. "memory management") becomes more complicated. Each of those objects also has member functions, probably a vtable, and a layout in memory: You may have something that's the same size as std::string and has members with the same names that do the same things, but it's not the same. You could "instantiate" the stack template for both that class and std::string, and the compiler would merrily generate the right code for each one, but once that code is generated, it is absolutely normal C++ code. When it calls a member function, it's calling the function through a function pointer. When it looks at a data member, it's using pointer arithmetic. In C++, names matter only in source code. Templates operate on names, but at runtime that's all been long forgotten. You could use a typecast and try to ram a hierarchically unrelated std::string "look-alike" (and size-alike) into your std::string stack, and that would seem to work until you tried to pull it back out and do anything with it. At that point, the code generated by the compiler will call wong entries in its vtable, play pointer arithmetic based on wildly wrong assumptions about its layout, and most likely crash.

When it comes to coping with heterogenous collections of objects, templates buy you nothing that you didn't already get with old-fashioned inheritance. You can't leave home without a well-designed class hierarchy, and that's that.

To conclude: C++ templates are an elaborate and very powerful mechanism for pretending, in some cases, that the language isn't bound by a rigid object oriented inheritance-hungry type system. Don't make too much of that "bound"/"rigid" rhetoric: That straitjacket of a type system lets the compiler generate wonderfully efficient code, and that's worth the price of admission for many applications.




1 All floating point literals in C++ are promoted to double, whether they need it or not (p 74, The C++ Programming Language 3rd ed. by Bjarne Stroustrup);

The D programming language (which is evolved from C++) also has templates. [1] The templates in C++ have been used for many, many purposes beyond typesafe generic functions and classes. (For an extreme example, see the Boost MPL Library: http://www.boost.org/libs/mpl/doc/index.html.) The templates in D were rethought from the ground up by looking at what people actually used templates for, and then making many of those things easier. [2]

Let's look at the examples from the previous writeup, re-written in D:

T1 min(T1, T2) (T1 a, T2 b) {
    return ( a < b ) ? a : b;
}

And here's the stack example in D:

class stack(T) {
private:
    // Use a built-in dynamic array.
    T[] m_data;
public:
    // Don't need a default constructor;
    // D initializes members by default.
    // Don't need a destructor; D is garbage collected.

    int count() { return m_data.length; }

    T pop() {
        T temp = m_data[length-1];
        // Array slicing
        m_data = m_data[0..length-1];
        return temp;
    }
    T top() { return m_data[length-1]; }

    void push(T newdata) {
        // In-place array concatenation operator
        m_data ~= newdata;
    }
}

Using this class is much the same:

stack!(int) st1;
stack!(char[]) st2;

st2.push("foo");
st1.push(456);

So those are simple enough. They are mostly equivalent to the C++ templates above. Aside from the streamlined template syntax (which is optional: you can still say "template" if you like), they do not really show off any new template features.

So, let's talk about static if using an example from the spec:

template INT(int SIZE) {
    static if (SIZE == 32)
        alias int INT;
    else static if (SIZE == 16)
        alias short INT;
    else
        static assert(false, "Invalid size!");
}

INT!(32) a; // a is an int
INT!(16) b; // b is a short
INT!(17) c; // error, static assert trips

Integer arguments to templates are nothing new, but static if is. Because D does not have a preprocesser, conditional compilation must be handled some other way. static if therefore replaces #if and #endif, and it has the very, very useful ability to access template arguments, which the preprocesser cannot do in C++. The static assert is analogous to #error in C++. If the expression in parends is false, it halts compilation.

static if can also be used to check whether a template argument is a given type:

template Foo(T) {
    static if (is(T == int)) {
        // do something useful ...
    } else static if (is(T == real)) {
        // do something else ...
    } else static assert(false, "Invalid type!");
}

The "is" expression is actually astoundingly powerful, and can be used for many other purposes:

import std.stdio;

template RetType(alias Fn) {
    static if (is(typeof(Fn) RET == function)) {
        alias RET RetType;
    } else static assert(false);
}

char[] foo() { return "monkey!"; }

void main() {
    writefln("foo's return type is: %s", typeid(RetType!(foo)));
}

static if also vastly simplifies some of the more complicated template metaprogramming tricks, making them look much more like a normal recursive function. This compile-time factorial example is from the spec: [2]

template factorial(int n) {
    static if (n == 1)
        const int factorial = 1;
    else
        const int factorial = n * factorial!(n-1);
}

String literals are also valid template parameters. Someone has even written a compile-time regular expression engine using D's templates. [2] (I am aware that there is such a thing for C++. However the D one simply operates on string literals, while the C++ one does some strange, fancy operator overloading stuff.)


Sources:

  1. http://www.digitalmars.com/d/template.html - The most up-to-date docs on D's templates.
  2. http://www.digitalmars.com/d/templates-revisited.html - A white paper on re-working C++'s templates.

ariels says re Template: All the examples you show with "static" are easily performed using C++ templates (using partial template specialization and typedefs).

/msg ariels

I am aware that static if does not really do anything that partial template specialization can't already achieve. The point is that (for example) the factorial template is done in one template and six lines what otherwise needs multiple templates and partial specialization to achieve. D's templates can be used by people who haven't studied the many tricks needed to leverage C++'s templates to these effects. They also scale better: These trivial examples may not show it, but this way of doing templates is more manageable in more complicated situations.

The point is making this other, second programming language look and behave much more like the first one it is built on top of.

I also did not enumerate all of the features of D templates. How easy is this in C++?

import some_c_library;

void foo() { }
void bar() { }

// Alias parameters can accept any symbol that can be
// evaluated at compile-time.
template wrap_func(alias Fn) {
    // Wrap a D function with an "extern(C)" interface.
    extern(C) void func() {
        Fn();
    }
}

void main() {
    // Some C function that takes a function pointer as an argument.
    c_function_with_callback(&wrap_func!(foo).func);
    c_function_with_callback(&wrap_func!(bar).func);
}

I humbly request that you examine the second-to-last example above, again. The one where I derive the return type of a function in five lines and one template. Can C++ really do that anywhere near as elegantly? Without resorting to that almost glorious kludge of the quasi-variadic template?

So yes! C++ can do it, too. But D does it so much better.

D is hardly perfect, of course. The garbage collector requires some special care when writing real-time applications, for instance. (Calling "new" happens in potentially unbound time.) Protection attributes are currently a little broken. (Whoops!) Requiring reference semantics on class instances means trivial classes can have more overhead than a C++ coder might be used to. (Structs still have value semantics. Interfacing to C would be impossible if they didn't. However, they don't get constructors or destructors. If they did, this might as well be C++.)

But templates are one thing D nailed.

Tem"plate (?), n.

Same as Templet.

 

© Webster 1913.

Log in or register to write something here or to contact authors.