Template (C ++)

from Wikipedia, the free encyclopedia

Templates (English for stencils or templates ) are a means of type parameterization in C ++ . Templates enable generic programming and type-safe containers.

In the C ++ standard library , templates for providing type-safe containers, such as B. lists, and for the implementation of generic algorithms, such as. B. sorting method used. The templates in C ++ are essentially inspired by the parameterizable modules in CLU and the generics in Ada .

In other programming languages ​​(e.g. Java or C # ) there is the concept of generic type , which is related to templates. Generic types, however, do not represent code generators , but only enable type-safe containers and algorithms.

Types of templates

There are three types of templates in C ++: function templates , class templates and variable templates :

Function templates

A function template (also incorrectly called a template function ) behaves like a function that accepts arguments of different types or provides different return types. The C ++ standard library, for example, contains the function template std::max(x, y)that returns the larger of the two arguments. It could be defined something like this:

template <typename T>
T max(T x, T y) {
    T value;
    
    if (x < y)
        value = y;
    else
        value = x;
    
    return value;
}

This template can be called just like a function:

cout << max(3, 7); // gibt 7 aus

Based on the arguments, the compiler decides that it is a call to max(int, int)and creates a variant of the function in which the generic type T is set to int .

The template parameter could also be specified explicitly:

cout << max<int>(3, 7); // gibt ebenfalls "7" aus

The function template max()can be instantiated for any type for which the comparison represents x < ya well-defined operation. Operator overloading is used for self-defined types to define the meaning of <for the type and thereby max()enable the use of for the type in question.

In interaction with the C ++ standard library, an enormous functionality opens up for self-defined types by defining a few operators. Only by defining a comparison operator <( weak ordering ), the standard algorithms std::sort(), std::stable_sort()and std::binary_search()applicable to the user-defined type.

Class templates

A class template (German: class template , also incorrectly called template class ) applies the same principle to classes. Class templates are often used to create generic containers. For example, the C ++ standard library includes a container that implements a linked list. To create a linked list of int , write std::list<int>. A linked list of objects of the data type std::stringbecomes std::list<std::string>. With list , a set of standard functions is defined that are always available, regardless of what type of argument is given in the angle brackets. The values ​​in angle brackets are called parameters. If a class template with its parameters is passed to the compiler, the latter can develop the template. It creates a separate template class for each parameter type. This is an ordinary class like any other. A distinction must be made between the terms class template and template class. Like the object and class , the template class is a variant of a class template.

Templates can be used for classes defined with class as well as for classes defined with struct and union . Namespaces, on the other hand, cannot be created as templates. A possibility to create type definitions as templates using "typedef" was added with C ++ 11.

Inheritance

Class templates, like normal classes, can appear in inheritance hierarchies both as base and as derived classes.

If a class template is defined with different class parameters, these are fundamentally not in any inheritance relationship - not even if the template parameters are in an inheritance relationship.

example
class Base {...};
class Derived: Base { ... };

Base* b = new Derived;  // OK. Automatische Typumwandlung, da Basisklasse.
std::vector<Base>* vb = new std::vector<Derived>;  // FEHLER!

There are two main reasons why such conversions are not allowed:

On the one hand there are technical reasons: One std::vector<T>saves its elements in an array at consecutive addresses. If an object of the type has Deriveda different size than an object of the type Base, then the memory layout of one std::vector<Base>no longer std::vector<Derived>matches that of one and access to the elements would fail.

On the other hand, there are also reasons for logic in the program: A container that contains elements of a base class refers to elements whose data type Baseis or Baseare derived from. On the one hand, it is more powerful than a container that can onlyDerived accept elements of the type , and on the other hand, less powerful, since it is only guaranteed Baseto return elements of the type . The std::vector<T>easiest way to illustrate this is to use the []operator.

example
class Base {...};
class Derived: Base { ... };

Base   * b = new Base;
Derived* d = new Derived;
std::vector<Base>   * vb = new std::vector<Base>;
std::vector<Derived>* vd = new std::vector<Derived>;

On the one hand, however, you can vb[0] = bnot vd[0] = bwrite. On the other hand, however, you can d = vd[0]not d = vb[0]write.

If it std::shared_ptrmakes sense to convert different versions of the same class template - as is the case with - a type conversion operator must be explicitly defined.

Variable templates

Since C ++ 14 it is also possible to define variable templates. This enables variables that logically belong together or are "the same" apart from the type to be appropriately identified:

// variable template
template<class T>
constexpr T Pi = T(3.1415926535897932385L);
 
// function template
template<class T>
T kreisflaeche(T r) {
    return Pi<T> * r * r;
}

The function kreisflaeche()can now be instantiated for different arithmetic data types and always accesses a constant that matches the parameter type Pi.

specialization

Templates can be specialized , i. In other words, class and function templates (for certain data types as template arguments) can be implemented separately. This allows a more efficient implementation for certain selected data types without changing the interface of the template. Many implementations of the C ++ standard library (for example that of the GCC ) also make use of this.

Specialization in class templates

The container class of std::vectorthe C ++ standard library can be implemented as a bitmap for the element type bool in order to save storage space. The class template also takes the information on handling the individual characters from the char_traits structure , which is specialized for the char data type and, for example, wchar_t . std::basic_string

The declaration of specializations is similar to that of normal templates. However, the angle brackets following the keyword template are empty, and the template parameters follow the function or class name.

example
template<>
class vector<bool> {
    // Implementierung von vector als Bitmap
};

Partial specialization

There is also what is known as partial specialization , which enables special cases to be handled within a template.

example
template<int zeilen, int spalten>
class Matrix {
    // Implementierung einer Matrix-Klasse
};

template<int zeilen>
class Matrix<zeilen, 1> {
    // Implementierung einer einspaltigen Matrix-Klasse
};

The instantiation is basically the same for the specialized class, only the code is generated from another class template, namely the specialization:

int main() {
    // erste Klasse
    Matrix<3,3> a;
    // teilweise spezialisiertes Klassen-Template (zweite Klasse)
    Matrix<15,1> b;
}

It is important to mention that both classes are independent of each other, i. h. inherit neither constructors or destructors nor member functions and data elements from each other.

Specialization in function templates

In contrast to class templates, according to the standard, function templates cannot be partially specialized (only completely). However, it is generally not advisable to specialize in function templates, as the rules for determining the “best-fitting” function can otherwise lead to unintuitive results.

By overloading function templates with other function templates, you can in most cases achieve the same thing as with the (not permitted) partial specialization. The extension itself is usually very intuitive:

// Generische Funktion
template<class T, class U>
void f(T a, U b) {}

// Überladenes Funktions-Template
template<class T>
void f(T a, int b) {}

// Vollständig spezialisiert; immer noch Template
template<>
void f(int a, int b) {}

However, it must be noted here that an explicit call such as f<int,int>() does not lead to the desired result. This call would call the generic function instead of the fully specialized one. A call to, f<int>()however, does not call the first overloaded template function, but the fully specialized one. If you omit explicit calls, everything usually works as it seems logical ( f(3, 3)calls the fully specialized function, f(3.5, 5)the partially specialized (first overloaded function) and f(3.5, 2.0)the generic calls ). One should therefore be careful with this type of specialization and, if possible, specialize immediately.

If for whatever reason this technique cannot be used in a specific case - e.g. B. If a template of class methods is to be specialized without expanding the class definition - the problem of specialization can also be shifted to a template of an auxiliary class:

class Example {
private:
    template<typename T>
    struct Frobnicator {
        static T do_frobnicate(T param);
    };

public:
    template<typename T>
    T frobnicate(T param);
};

template<typename T>
T Example::frobnicate(T param) {
    // Frobnicator soll die eigentliche Arbeit verrichten
    return Frobnicator<T>::do_frobnicate(param);
}

template<typename T>
T Example::Frobnicator<T>::do_frobnicate(T param) {
    // Standardimplementierung ...
}

template<>
int Example::Frobnicator<int>::do_frobnicate(int param) {
    // ints werden auf andere Weise "frobnifiziert"
    return (param << 3) + (param % 7) - param + foobar;
}

Template parameters

Templates can have four types of parameters: type parameters, non-type parameters, template parameters and so-called parameter packs, with which templates can be defined with a variable number of parameters.

Type parameters

Templates with type parameters roughly correspond to the generic types of other programming languages. Any data type can be used as the type parameter, although the number of parameter types with which the template can be instantiated is limited depending on the use of the type parameter within the template.

The containers of the C ++ standard library use type parameters, among other things, to provide suitable containers for all data types, including user-defined ones.

template<typename T>
class Vector {
public:
    Vector(): rep(0) {}
    Vector(int _size): rep(new T[_size]), size(_size) {}
    ~Vector() { delete[] rep; }

private:
    T* rep;
    int size;
};

Non-type parameters

Non-type template parameters are constant values ​​known at the time of compilation , with which variables, procedures or predicates can be transferred as template parameters. The following are permitted as non-type template parameters:

  • integer constants (including character constants),
  • Pointer constants (data and function pointers, including pointers to member variables and functions) and
  • String constants.

Non-type parameters are used, e.g. B. as a size specification for std::arrayor as a sorting and search criterion for many algorithms in the standard library, such. B. std::sort, std::find_ifor std::for_each.

Template templates

As template templates , constructions are referred to in which templates in turn take templates as parameters. They provide another abstraction mechanism. The following example shows both the type and the container used; the latter with the help of a template template parameter:

template <template <typename, typename> class Container, typename Type>
class Example {
    Container<Type, std::allocator <Type> > baz;
};

Example of use:

Example <std::deque, int> example;

Parameter packs

Since C ++ 11 there is the possibility to define templates with a variable number of template parameters. As with functions and macros with a variable number of parameters, these are ...marked with :

template<typename... Values> class tuple {
    // Definition ausgelassen
};

// Verwendung:
tuple<int, int, char, std::vector<int>, double> t;

See also

literature

  • David Vandervoorde, Nicolai M. Josuttis: C ++ Templates . The Complete Guide. Addison-Wesley Professional, 2003, ISBN 0-201-73484-2 .

Individual evidence

  1. Bjarne Stroustrup : The C ++ programming language . 4th edition. Addison-Wesley, 2009, ISBN 978-3-8273-2823-6 .
  2. Oracle Technology Network for Java Developers | Oracle Technology Network | Oracle. Retrieved May 26, 2017 .
  3. An Introduction to C # Generics. Retrieved May 26, 2017 (English).
  4. Herb Sutter: Why Not Specialize Function Templates? In: C / C ++ Users Journal . tape 7 , no. July 19 , 2001 ( gotw.ca ).