Sunday, February 06, 2005

Templates Indepth

  • Non type template parameters

  • In the code sample below N is a non type template parameter.

    template<class T, size_t N> class Stack {
    T data[N]; // Fixed capacity is N
    size_t count;
    public:
    void push(const T& t);
    // Etc.
    };


    * The non-type template parameter MUST be an integral value that is known at compile
    time

  • Default template arguments

  • In the following code sample the second template parameter has a default argument.


    template<class T, size_t N = 100> class Stack {
    T data[N]; // Fixed capacity is N
    size_t count;
    public:
    void push(const T& t);
    // Etc.
    };



    * You can provide default arguments for template parameters in class templates, but NOT in function templates. (why?)

    * They should only be defined once, the first time a template declaration or definition is seen by the compiler.

    * Once you introduce a default argument, all the subsequent template parameters must also have defaults.

    * You can choose to provide defaults for all arguments, but you must use an empty set of brackets when declaring an instance so that the compiler knows that a class template is involved.

    template<class T = int, size_t N = 100> // Both defaulted
    class Stack {
    T data[N]; // Fixed capacity is N
    size_t count;
    public:
    void push(const T& t);
    // Etc.
    };


    * If default arguments happen to be templatized as below, put the space between the last two right angle bracket characters as it prevents the compiler from interpreting those two characters (>>) as the right-shift operator.

    template<class T, class Allocator = allocator<T> >
    class vector;


  • Template template arguments

  • Following code sample is an example of a class template with a template parameter as the argument.

    template<class T, template<class> class Seq>
    class Container {
    Seq<T> seq;
    public:
    void append(const T& t) { seq.push_back(t); }
    T* begin() { return seq.begin(); }
    T* end() { return seq.end(); }
    };


    * The class Seq is another class template. For example an Array class template could be a trivial sequence container.

    * When the compiler looks at the inner parameters of a template template parameter, default arguments are not consideed so you MUST repeat the defaults in order to get a exact match.

    The default dimension of 10 is required in the line:

    template<class T, template<class, size_t = 10> class Seq>


    * The above situation is the ONLY exception to the rule that default arguments must not be repeated in a compilation unit.

  • The "typename" keyword


  • * If a type referred to inside template code is qualified by a template type parameter, you must use the typename keyword as a prefix, unless it appears in a base class specification or initializer list in the same scope (in which case you MUST not).

    * Without "typename", the compiler would assume that the expression Seq::iterator is not a type in the following code segment. It will mistake it for a static data member.

    #include <iostream>
    #include <list>
    #include <memory>
    #include <vector>
    using namespace std;

    template<class T, template<class U, class = allocator<U> >
    class Seq>
    void printSeq(Seq<T>& seq) {
    for(typename Seq<T>::iterator b = seq.begin();
    b != seq.end();)
    cout << *b++ << endl;
    }



    * "typename can be used as an option to the keyword "class" within the template argument list of a template definition.

  • Member Function Templates

  • The complex template in C++ Standard Library has a type parameter meant to represent an underlying floating-point type to hold the real and imaginary parts of a complex number. The following code snippet from the standard library shows a member-template constructor in the complex class template:

    template<typename T> class complex {
    public:
    template<class X> complex(const complex<X>&);
    ...
    ...
    };

    usage
    complex<float> z(1, 2);
    complex<double> w(z);


    Here the member templates enables flexible conversion easy. The complex template parameter T is double whereas X is a float.

    * Defining a template within a template is a nesting operation, so the prefixes that introduce the templates must reflect this nesting if you define the member template outside the outer class definition

    template<typename T>
    template<typename X>
    complex<T>::complex(const complex<X>& c) {/* Body here… */}


    With this a vector of ints can be used to initialize a vector of doubles. which is legitimate conversion.

    * Member templates can also be classes. (They don’t need to be functions.)

    * Member template functions cannot be declared virtual. Current compiler technology expects to be able to determine the size of a class’s virtual function table when the class is parsed.

  • Function Templates Caveats

  • *While instantiating function templates you can often omit the template arguments. One MUST always use angle brackets when instantiating class.
    * Function templates can deduce the type to be used in templates from the types of the function arguments.
    * Default template arguments are not even allowed with function templates as opposed to class templates. (why?)
    * If template arguments are avoided when instantiating function templates NO standard conversion are performed. For eg. Consider the following function template


    template<typename T, typename U>
    const T& min(const T& a, const U& b) {
    return (a < b) ? a : b;
    }

    Will fail (As compiler does not know which type to deduce. Double or int?):
    int z = min(x, j); // x is a double ; j is int
    Will pass:
    int z = min<double>(x, j);
    Will pass:
    int z = min(x, (double)j);


    * If the RETURN type is an INDEPENDANT template parameter the function MUST be called with explicit type specification. Consider the following example where R is a template parameter return type.


    template<typename R, typename P>
    R implicit_cast(const P& p) {
    return p;
    }

    int main() {
    int i = 1;
    float x = implicit_cast<float>(i);
    int j = implicit_cast<int>(x);
    //! char* p = implicit_cast<char*>(i); // Fails because there is no conversion from int to char*
    }


    If you interchange R and P in the template parameter list near the top of the file, it will be impossible to compile this program because the return type will remain unspecified

0 Comments:

Post a Comment

<< Home