banner



Templates Is A Runtime Mechanism In Cpp?

C++ Tutorial - Templates - 2020

cplusplus_icon.png

Bookmark and Share




bogotobogo.com site search:


Templates

We use templates when we demand functions/classes that apply the same algorithm to a several types. So we tin can utilise the same function/class regardless of the types of the argument or result.

In other words, a template is a mechanism that allows a programmer to use types every bit parameters for a form or a function. The compiler then generates a specific grade or office when we afterwards provide specific types equally arguments. In a sense, templates provide static (compile-time) polymorphism, as opposed to dynamic (run-time) polymorphism. A function/grade defined using template is chosen a generic part/form, and the ability to utilize and create generic functions/classes is i of the fundamental features of C++.

Nosotros can think of a class template as a type generator. The process of generating types from a grade template is chosen specialization or template instantiation.

In most of the cases, template instantiation is very complicated, only that complication is in the domain of compiler writer, non the template user. Template instantiation takes identify at compile fourth dimension or link time, non at run time.

The syntax is:

template <course T> function_declaration;        

or

template <typename T> function_declaration;        

We can utilise either of construct. They are not dissimilar since both expressions accept exactly the same meaning and deport exactly the same fashion.

Let'southward have one example of how a template generates a code. We have a function similar this:

void f(vector<string>&v;) {     five.push_back("Steve Jobs"); }        

The compiler, when it sees 5.push_back("Steve Jobs"), generates a function something like this ("Programming Principles and Practice Using C++" by Stroustrup, 2009)

void vector<string>::push_back(const string& s){...}        

from the following template definition:

template<typename T> void vector<T>::push_back(const T& s){...}        

The procedure of generating types from a given template arguments is chosen specialization or template instantiation. For example, vector<int> and vector<Node*> are said to be a specialization of vector.

Templates are the foundation of generic programming. Generic programming relies on polymorphism. Though there are several differences between OOP (class hierarchies and virtual functions) and generic programming (templates), the major difference is:

  1. Generic (templates) : compile time resolution.
     The choice of function invoked when nosotros use is determined by the compiler at compile time.
  2. OOP (virtual functions) : run time resolution.

Generic programming lets the states write classes and functions that are polymorphic beyond unrelated types at compile fourth dimension. A single class or function can be used to manipulate objects of a diverseness of types. The standard library containers, iterators, and algorithms are examples of generic programming. Nosotros tin apply library classes and functions on any kind of type.

When we parameterize a class, nosotros get a class template, and when we parameterize a function, we become a role template.

So, what exercise people actually use template for?

  1. When performance is essential.
  2. When flexibility in combining data from several types is essential.

But notation that, the flexibility and operation come up at the toll of poor error diagnostics and poor error messages.

Terminology of Templates

Let's review the terminology of template using the following example implementing a generic stack form. This department of terminology is borrowed from "API design for C++" past Martin Reddy, 2011.

template <typename T> class Stack { public: 	void push button(T val); 	T pop(); 	bool isEmpty() const; individual: 	std:vector<T> mStack; };        
  1. Template Parameters

    These names are listed after the template keyword in a template declarations. Equally shown in the case, T is the single template parameter specified in the Stack class.

  2. Template Arguments

    These are substituted for template parameters during specialization. In our instance, given a specialization Stack<int>, the int is a template statement.

  3. Instantiation

    This is when the compiler generates a regular class, method, or part by substituting each of the template'south parameters with a physical type. This can happen implicitly when we create an object based on a template or explicitly if we want to control when the code generation happens. For case, the following code creates ii specific stack instances and will normally crusade the compiler to generate code for these ii unlike types:

    Stack<T> myIntStack; Stack<T> myStringStack;            
  4. Implicit Instantiation

    This is when the compiler decides when to generate code for our template instances. Leaving the decision to the compiler ways that it must find an appropriate place to insert the code, and it must likewise make sure that only ane example of the code exists to avoid duplicate symbol errors. This is not-little trouble and can cause actress bloat in our object files or longer compile time. Near importantly, implicit instantiation means that we accept to include the template definitions in our header files then that the compiler has an access to the definitions whenever it needs to generate the instantiation lawmaking.

  5. Emplicit Instantiation

    This is when the programmer determine when the compiler should generate the code for a specific specialization. This can make for much more efficient compilation and link times because the compiler no longer needs to maintain bookkeeping data for all of its implicit instantiations. The onus, however, is then placed on the developer to ensure that a particular specialization is explicitly instantiated merely once. So, explicit instantiation allows usa to move the template implementation into the .cpp file, and so hide from the user.

  6. Lazy Instantiation

    This describes the standard implicit instantation behavior of a C++ compiler wherein it will only generate lawmaking for the parts of a template that are actually used. Given the previous 2 instantiations, for example, if we never called isEmpty() on the myStringStack object, so the compiler would not generate code for the std::string specialization of that method. This ways that we tin can instantiate a template with a type that can exist used by some, but not all methods of a course template. If 1 method uses the >= operator, but the type we desire to instantiate does not ascertain this operator. This is fine equally long as we don't telephone call the detail method that attempts to use the >= operator.

  7. Specialization

    When a template is instantiated, the resulting class, method, or function is called a specialization. More specifically, this is an instantiated (or generated) specialization. However, the term specialization tin can also be used when we provide a custom implementation for a office by specifying physical types for all the template parameters. The following is chosen an explicit specialization:

    tempate<> void Stack<int>::push button(int val) {     // integer specific push implementation }            
  8. Partial Specialization

    This is when we provide a specialization of the template for a subset of all possible cases. In other words, we specialize one feature of the template but nonetheless allow the user to specify other features. For example, if our template accepts multiple parameters, nosotros could partially specialize it by defining a case where nosotros specify a concrete blazon for only one of the parameters. In our Stack example with a single template parameter, we could partially specialize this template to specifically handle pointers(*) to whatever blazon T. This withal lets users create a stack of any type, but it too lets usa write specific logic to handle the case where users create a stack of pointers. This partially specialized class declaration looks like this:

    template <typename T> class Stack<T*> { public: 	void push(T* val); 	T* popular(); 	bool isEmpty() const; private: 	std:vector<T*> mStack; };            

Pros and Cons of Templates

  1. Pros

    1. It provides usa type-safe, efficient generic containers and generic algorithms
    2. The master reason for using C++ and templates is the trade-offs in performance and maintainability outweigh the bigger size of the resulting code and longer compile times.
    3. The drawbacks of not using them are likely to be much greater.
  2. Cons

    1. Templates can lead to slower compile-times and maybe larger executable.
    2. Compilers oft produce incomprehensible poor error diagnostics and poor error messages.
    3. The design of the STL collections tends to atomic number 82 to a lot of copying of objects. The original smart arrow, std::auto_ptr, wasn't suitable for use in most collections. Things could be improved if we use other smart pointers from heave or C11.

Function Templates

Here is a simple instance of how nosotros employ the template for a office which gives the states the minimum value.

#include <iostream>  template <grade T> const T& min(const T& a, const T& b) { 	return a < b ? a : b; }  int principal()  { 	int ten = 10, y = 20; 	long xLong = 100, yLong = 200; 	int minimum = min<int>(ten, y); 	long minimumLong = min<long>(xLong,yLong); 	std::cout << "minimum = " << minimum << std::endl; 	std::cout << "minimumLong = " << minimumLong << std::endl; }        

If nosotros drop <int> or <long>, the compiler still can exercise the job since it nevertheless can get the data about the type from the the function argument.

A function template tin be declared inline in the aforementioned way equally a nontemplate function. The specifier is placed following the template parameter listing and before the return type. Information technology shouldn't be placed in front of the template keyword. So, it looks similar this:

template <class T>          inline          const T& min(const T& a, const T& b) {        

But not this:

          inline          template <grade T> const T& min(const T& a, const T& b) {        

We tin as well ascertain function templates that accept more than 1 type parameter, merely by specifying more than template parameters between the bending brackets as in the instance below.

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

Class Templates

Class tin exist generic using template.

template <class T> class Complx { private: 	T real, imag; public: 	Complx(T&, T&); 	T& getReal(); 	T& getImag(); };  template <course T> Complx<T>::Complx(T& a, T& b) { 	real = a; 	imag = b; }  template <grade T> T& Complx<T>::getReal() { 	return real; }  template <class T> T& Complx<T>::getImag() { 	return imag; }  #include <iostream>  int chief()  { 	double i=100, j=200; 	Complx <double> myComplx(i,j);  	std::cout <<"My circuitous is " << myComplx.getReal() << "," 		<< myComplx.getImag() << std::endl; }        

Unproblematic Template Exercise

Initial lawmaking

We have the following vector class:

course vector { 	int sz;		// number of elements 	double* elem;	// address of commencement chemical element 	int infinite;	// number of elements plus free-space 			// slots for new elements public: 	vector() : sz(0), elem(0), infinite(0) {} 	vector(int s) : sz(southward), elem(new double[s]), infinite(due south) { 		for(int i = 0; i < sz; ++i) elem[i] = 0; 	} 	vector(const vector&); 	vector& operator=(const vector & v);  	~vector() { delete[] elem; }  	double& operator[](int n); 	const double& operator[](int due north) const;  	int size() const { return sz; } 	int chapters() const { return space; }  	void reserve(int alloc_size); 	void resize(int resize_size); 	void push_back(double d); };  void vector::reserve(int alloc_size) { 	if(alloc_size <= space) return; 	double *p = new double[alloc_size]; 	for(int i = 0; i < sz; ++i) p[i] = elem[i]; 	delete[] elem; 	elem = p; 	space = alloc_size; }  vector& vector::operator=(const vector & v) { 	if(this == &v;) return *this;  	if(v.sz <= space) { 		for(int i = 0; i < v.sz; ++i)  			elem[i] = v.elem[i]; 		sz = v.sz; 		render *this; 	}  	double *p = new double[v.sz]; 	for(int i = 0; i < v.sz; ++i)  		p[i] = v.elem[i]; 	delete[] elem; 	space = sz = v.sz; 	elem = p; 	render *this; }  void vector::resize(int resize_size) { 	reserve(resize_size); 	for(int i = 0; i < resize_size; ++i) elem[i] = 0; 	sz = resize_size; }  void vector::push_back(double d){ 	if(space == 0)  		reserve(10); 	else if(sz == space)  		reserve(ii*space); 	elem[sz] = d; 	++sz; }        

Permit's catechumen the lawmaking using template parameters by replacing double with T:

#include <iostream>  using namespace std;  template<class          T> class vector { 	int sz;		// number of elements          T* elem;	// address of first element 	int space;	// number of elements plus free-infinite  public: 	vector() : sz(0), elem(0), space(0) {} 	vector(int s) : sz(due south), elem(new          T[s]), space(s) { 		for(int i = 0; i < sz; ++i) elem[i] = 0; 	} 	vector(const vector&); 	vector& operator=(const vector & v);  	~vector() { delete[] elem; }          T& at(int due north); 	const          T& at(int due north) const;          T& operator[](int n); 	const          T& operator[](int north) const;  	int size() const { return sz; } 	int capacity() const { return space; }  	void reserve(int alloc_size); 	void resize(int resize_size); 	void push_back(const          T& d); };  template<class          T> void vector<T>::reserve(int alloc_size) { 	if(alloc_size <= space) return;          T          *p = new          T[alloc_size]; 	for(int i = 0; i < sz; ++i) p[i] = elem[i]; 	delete[] elem; 	elem = p; 	space = alloc_size; }  template<class          T>          T& vector<T>::at(int north) { 	if(n < 0 || sz <= north) throw out_of_range(); 	return elem[n]; }  template<grade          T> const          T& vector<T>::at(int n) const { 	if(n < 0 || sz <= n) throw out_of_range(); 	return elem[n]; }  template<grade          T>          T& vector<T>::operator[](int n) { 	return elem[due north]; }  template<class          T> const          T& vector<T>::operator[](int due north) const {  	return elem[n];  }  template<class          T> vector<T>& vector<T>::operator=(const vector<T> & v) { 	if(this == &v;) render *this;  	if(v.sz <= space) { 		for(int i = 0; i < five.sz; ++i)  			elem[i] = v.elem[i]; 		sz = 5.sz; 		return *this; 	}          T          *p = new          T[v.sz]; 	for(int i = 0; i < v.sz; ++i)  		p[i] = v.elem[i]; 	delete[] elem; 	space = sz = five.sz; 	elem = p; 	return *this; }  template<course          T> void vector<T>::resize(int resize_size) { 	reserve(resize_size); 	for(int i = 0; i < resize_size; ++i) elem[i] = 0; 	sz = resize_size; }  template<class          T> void vector<T>::push_back(const          T& d){ 	if(space == 0)  		reserve(10); 	else if(sz == infinite)  		reserve(2*space); 	elem[sz] = d; 	++sz; }  int main() { 	vector<double> dv(3); 	dv.resize(5); 	for(int i = 0; i < dv.size(); ++i) { 		cout << dv[i] << " " ;      //0 0 0 0 0 	}         return 0; }        

I thing we should notation in the chief() is that we used the type double which has the default value of 0. For the resize() fucntion, nosotros could have done it by providing a way of initializing the vector:

template <class T> void vector<T>::resize(int sz, T def=T()); ... vector<double> 5 five.resize(ten);      // add together 10 copies of double() which is 0.0 v.resize(10, ii.0)  // add x copies of 2.0        

Still, as shown in the example below, there is an outcome of how do we handle a type when it does not accept a default value. For instance, vector<X> where X does not have default value:

#include <vector> struct X { 	X(int) {}; 	~X() {}; };  int main() { 	std::vector<X> fifteen;          /* tries to make three Xs */ 	//std::vector<X> xv2(3);  // error: no appropriate default constructor bachelor         render 0; }        

So, in full general, if we want to implement something similar vector container where we do not have proper mode of initialization for the type that does not accept default value. Also, more trickier is the issue of destroying the elements when we are finished with them. So, let's look at what the standard library provides to handle those bug.

The allocator class

The standard library, fortunately, provides a form allocator, which provides uninitialized retentiveness. The allocator class is a template that provides typed memory resource allotment, object structure and destruction. The allocator class separates allocation and object construction. When an allocator object allocates retentiveness, it allocates space that is suitably sized and aligned to hold objects of the given type. However, the retention it allocates is unconstructed. Users of it should separately construct and destroy objects placed in the memory it allocates.

The class looks like this ("Programming Principles and Practice Using C++" past Stroustrup, 2009):

template<class T> class          allocator          { public: 	// classify space for north objects of type T 	T* classify(int n);  	// deallocate northward objects of type T starting at p 	void deallocate(T*p, int n);  	// construct a T with the value five in p 	void construct(T* p, const T& v);  	// destroy the T in p 	void destroy(T* p); };        

The four operators permit us to:

  1. Allocate memory of a size big enough to hold an object of blazon T without initializing.
  2. Deallocate uninitialized space of a size large enough for an object of type T.
  3. Construct an object of type T in uninitialized space. The construct office uses the copy constructor for type T to copy the value v into the element denoted by p.
  4. Destroy an object of type T, thus returning its space to the uninitialized state.

The allocator is exactly what we demand for implementing vector<T>::reserve().

Converted code A using template

#include <retention>  template<class T> class vector { 	int sz;		// number of elements 	T* elem;	// address of commencement chemical element 	int infinite;	// number of elements plus free-space 			// slots for new elements          std::allocator<T> a;          public: 	vector() : sz(0), elem(0), infinite(0) {} 	vector(int south) : sz(due south), elem(new T[s]), space(s) { 		for(int i = 0; i < sz; ++i) elem[i] = 0; 	} 	vector(const vector&); 	vector& operator=(const vector & 5);  	~vector() { delete[] elem; }  	T& at(int n); 	const T& at(int n) const;  	T& operator[](int n); 	const T& operator[](int n) const;  	int size() const { render sz; } 	int capacity() const { return space; }  	void reserve(int alloc_size); 	void resize(int resize_size, T val); 	void push_back(const T& d); };  template<class T> void vector<T>::reserve(int alloc_size) { 	if(alloc_size <= infinite) render; 	T* p =          a.allocate(alloc_size); 	for(int i = 0; i < sz; ++i)          a.construct(&p;[i], elem[i]); 	for(int i = 0; i < sz; ++i)          a.destroy(&elem;[i]);          a.deallocate(elem,space); 	elem = p; 	space = alloc_size; }  template<class T>  T& vector<T>::at(int n) { 	if(northward < 0 || sz <= n) throw out_of_range(); 	return elem[n]; }  template<grade T>  const T& vector<T>::at(int northward) const { 	if(due north < 0 || sz <= n) throw out_of_range(); 	return elem[n]; }  template<course T> T& vector<T>::operator[](int n) { 	return elem[n]; }  template<class T> const T& vector<T>::operator[](int n) const {  	return elem[n];  }  template<class T> vector<T>& vector<T>::operator=(const vector<T> & five) { 	if(this == &v;) return *this;  	if(v.sz <= infinite) { 		for(int i = 0; i < v.sz; ++i)  			elem[i] = v.elem[i]; 		sz = v.sz; 		render *this; 	}  	T* p =          a.allocate(alloc_size); 	for(int i = 0; i < sz; ++i)          a.constructa.destroy(&five.elem;[i]);          a.deallocate(elem,space);  	infinite = sz = 5.sz; 	elem = p; 	return *this; }  template<class T> void vector<T>::resize(int resize_size, T val = T()) { 	reserve(resize_size); 	for(int i = 0; i < resize_size; ++i)          a.construct(&elem;[i], val); 	for(int i = 0; i < resize_size; ++i)          a.destroy(&elem;[i]); 	sz = resize_size; }  template<form T> void vector<T>::push_back(const T& d){ 	if(space == 0)  		reserve(10); 	else if(sz == space)  		reserve(2*space); 	elem[sz] = d; 	++sz; }        

Converted code B using template

Nosotros tin start by passing vector an allocator parameter:

template<class T, class A = allocator<T>> form vector  { 	A a;	// using allocate to handle memory for elements 	... };        

With the handle defined as above line, the new lawmaking looks like this:

#include <memory>  template<course T,          form A = allocator<T>> grade vector { 	int sz;		// number of elements 	T* elem;	// address of first element 	int space;	// number of elements plus gratuitous-infinite 			// slots for new elements          A a;          public: 	vector() : sz(0), elem(0), space(0) {} 	vector(int south) : sz(southward), elem(new T[due south]), space(due south) { 		for(int i = 0; i < sz; ++i) elem[i] = 0; 	} 	vector(const vector&); 	vector& operator=(const vector & v);  	~vector() { delete[] elem; }  	T& at(int n); 	const T& at(int n) const;  	T& operator[](int n); 	const T& operator[](int north) const;  	int size() const { render sz; } 	int capacity() const { return space; }  	void reserve(int alloc_size); 	void resize(int resize_size, T val); 	void push_back(const T& d); };  template<class T,          class A          > void vector<T,A>::reserve(int alloc_size) { 	if(alloc_size <= infinite) return; 	T* p =          a.allocate(alloc_size); 	for(int i = 0; i < sz; ++i)          a.construct(&p;[i], elem[i]); 	for(int i = 0; i < sz; ++i)          a.destroy(&elem;[i]);          a.deallocate(elem,space); 	elem = p; 	space = alloc_size; }  template<class T,          course A>  T& vector<T,A>::at(int north) { 	if(north < 0 || sz <= north) throw out_of_range(); 	render elem[n]; }  template<class T,          class A>  const T& vector<T,A>::at(int n) const { 	if(n < 0 || sz <= north) throw out_of_range(); 	return elem[due north]; }  template<grade T,          class A> T& vector<T,A>::operator[](int n) { 	return elem[n]; }  template<course T,          class A> const T& vector<T,A>::operator[](int due north) const {  	return elem[n];  }  template<class T,          class A> vector<T,A>& vector<T,A>::operator=(const vector<T,A> & v) { 	if(this == &five;) return *this;  	if(five.sz <= space) { 		for(int i = 0; i < v.sz; ++i)  			elem[i] = five.elem[i]; 		sz = v.sz; 		return *this; 	}  	T* p =          a.allocate(alloc_size); 	for(int i = 0; i < sz; ++i)          a.construct(&p;[i], v.elem[i]); 	for(int i = 0; i < sz; ++i)          a.destroy(&v.elem;[i]);          a.deallocate(elem,space);  	space = sz = v.sz; 	elem = p; 	return *this; }  template<class T,          grade A> void vector<T,A>::resize(int resize_size, T val = T()) { 	reserve(resize_size); 	for(int i = 0; i < resize_size; ++i)          a.construct(&elem;[i], val); 	for(int i = 0; i < resize_size; ++i)          a.destroy(&elem;[i]); 	sz = resize_size; }  template<class T,          class A> void vector<T,A>::push_back(const T& d){ 	if(space == 0)  		reserve(ten); 	else if(sz == space)  		reserve(2*space); 	elem[sz] = d; 	++sz; }        

Nontype Template Parameters

Information technology is useful to parameterize classes with types. How well-nigh parameterizing classes with nontype things, such as string values, integral expressions, pointers, and references?

We can employ a parameter which is not a type. A nontype parameter is considered as office of the type. For the standard form bitset<>, we can pass the number of $.25 as the template statement.

bitset<32> b32;		// bitset with 32 $.25 bitset<64> b64;		// bitset with 64 bits        

The bitsets have different types because they use different template arguments. Then, we can't assign or compare them.

This nontype parameter is replaced by value when the function is called. The type of that value is specified in the template parameter list. In the case below, the function template declares arrayInit as a function template with one blazon and one nontype template parameter. The function itself takes a single parameter, which is a reference to an array.

template <class T,          size_t N> void arrayInit(T (&a;)[N])  { 	for (size_t i = 0; i != N; ++i) a[i] = 0; }  int main()  { 	int i[x]; 	double d[twenty]; 	arrayInit(i);	// arrayInit(int(&)[x]) 	arrayInit(d);	// arrayInit(double (&)[xx]) }        

A template nontype parameter is a constant value inside the template definition. We can employ a nontype parameter when we demand constant expressions. In the example, we need that to specify the size of an array. So, when arrayInit is called, the compiler figures out the value of the nontype parameter from the array argument.

Here is some other example using integer as a non-type parameters. In the code below, nosotros want to assign values for all the elements of a vector. So, nosotros prepare the template parameter with an integere value as non-blazon, and every bit nosotros traverse each element of the vector container, we ready the value we want past calling setValue().

#include <iostream> #include <vector> #include <algorithm> using namespace std;  template <int          val> void setValue(int& elem) { 	elem = val; }  grade PrintElements { public: 	void operator()(int& elm) const {cout << elm << ' ';} };  int main() { 	int size = 5; 	vector<int> v(size);         PrintElements print_it; 	for_each(v.begin(), 5.end(), print_it); 	for_each(v.begin(), v.finish(), setValue<10>); 	for_each(five.begin(), v.end(), print_it); 	render 0; }        

Output:

0 0 0 0 0 x 10 10 ten ten        

More On Template Parameters

The proper name used for a template parameter has not intrinsic meaning. In the previous case, the min()'south template proper name T could exist annihilation:

template <class DontCare> const DontCarer& min(const DontCare& a, const DontCare& b) { return a < b ? a : b; }        

But we need to know whether the parameter is a blazon parameter or a nontype parameter. If it is a type parameter, so nosotros know that the parameter represents an as withal unknown type. If it is a nontype parameter, nosotros know information technology is an even so unknown value.

Scope of Template Parameter

          typedef float T;          template <grade T> T& min(const T& a, const T& b)  {  	T tmp = a < b ? a : b;  	return tmp; }        

The global typedef that defines T as bladder is hidden by the type parameter named T. And so, tmp is not a float, it still has the type of the template parameter T, not that of the global typedef. In other words, the blazon of tmp is whatsoever type gets spring to the template parameter T.

Template Parameter Name

A proper name used as a template parameter may not be reused within the template:

template <class T> T& min(const T& a, const T& b)  {          typedef float T;          // mistake: redeclaration of template parameter T 	T tmp = a < b ? a : b;  	return tmp; }        

So, the proper name of a template parameter can exist used only one time within the same template parameter listing:

// error: illegal reuse of template parameter name T template <grade T, classT> T& min(const T& a, const T& b)        

In a function template parameter list, the keywords typename and class tin be used interchangeably. But when want to use types inside a function template, we must tell the compiler that the proper noun we are using refers to a blazon. Nosotros must be explicit because the compiler cannot tell when a name defined by a type parameter is a type or a value:

template <typename T> void foo(const T& vector) {          T::const_iterator* information technology;          }        

This appears that we're declaring it as a arrow to a T::const_iterator. Information technology'south because we know that T::const_iterator is a type. But what if T::const_iterator isn't a type?

If T::const_iterator is a type, and so the line is a declaration. But if T::const_iterator is an object, then it's a multiplication. We know that const_iterator must be a member of the type jump to T, simply we practise non know whether const_iterator is the name of a type or a data member. Until T is known, there's no way to know whether T::const_iterator is a type or isn't. When the template foo is parsed, T isn't known. The compiler, by default, assumes that it's a data fellow member not type. In other words, T::const_iterator is assumed to not exist a type.

And so, if we want the compiler to treat const_iterator as a type, then we must explicitly tell the compiler to practice so:

template <typename T> void foo(const T& vector) {          typename          T::const_iterator* it; }        

By writing typename T::const_iterator, we indicate that member const_iterator of the type jump to T is the name of a type.

Allow'southward take a wait at similar example:

template                      class A { 	typename T::MyType * ptr; 	... }                  

The typename is used to clarify that MyType is a blazon of grade T. And so, ptr is a arrow to the type T::MyType. Without typename, MyType would exist considered a static member. So,

T::MyType * ptr        

would be a multiplication of value MyType of type T with ptr.

Scott Meyers called a variable like T::const_iterator a nested dependent blazon proper noun.

So, the general rule is: anytime we refer to a nested dependent type name in a template, we must immediately precede it past the word typename becuase in C++, whatsoever identifier of a template is considered to be a value, except if it is qualified by typename.

Check another case when we need typename - Generic Linked Listing. Without the specification, we'll probably get the following mistake from Visual Studio 2010:

dependent name is non a type prefix with 'typename' to point a type        

Permit's look at another example of typename:

template<typename T> void foo(T it) {          typename iterator_traits<T>::value_type          tmp(*it); }        

Information technology declares a local variable, tmp, which is the same blazon equally what T objects point to, and it initializes tmp with the object that T points to. Then, if T is list<string>iterator, tmp is of type string.

Let's talk about the iterator_traits. When we write a generic code, we may need the blazon of elements to which the iterator refers. Then, C++ STL provides a special template structure to ascertain the iterator_traits. This structure has data regarding an iterator. It is used equally a common interface for all the type definitions an iterator should have, such as the category, the blazon of the elements, and so on:

template <class T> struct iterator_traits {      typedef typename T::value_type         value_type;      typedef typename T::difference_type    difference_type      typedef typename T::iterator_category  iterator_category        typedef typename T::pointer            arrow;      typedef typename T::reference          reference; };        

Hither, T stands for the blazon of the iterator. Then, the following expression yields the value type of iterator type T:

typename std::iterator_traits<T>::value_type        

iterator_traits is and then named because it describes argument'due south proterty/traits. The most important feature of traits templates is that they provide u.s.a. a way to associate information with a type non-intrusively. In other words, if we take a new parcel of code which gives usa some iterator-like types chosen iter_like that refers to a double, we can assign it a value_type without whatever disturbance to our existing code. All we have to do is to add an explicit specialization of iterator_traits, and foo() volition see the type double when it asks well-nigh the value_type of new packet's iterator:

namespace std {      template<>      struct iterator_traits<NewPackage::iter_like>      {          typedef double value_type;          ...      }; }        

This non-intrusive aspect of traits is exactly what makes iterator_traits piece of work for pointers: the standard library contains the following partial specialization of iterator_traits:

template <course T> struct iterator_traits<T*>  {       typedef T                               value_type;       typedef ptrdiff_t                       difference_type;       typedef random_access_iterator_tag      iterator_catogory;       typedef T*                              pointer;       typedef T&                              reference;         }        

So, for any type T*, it is defined that it has the random access iterator category, and thanks to the indirection through iterator_traits, generic functions tin can now access an iterator's associated types uniformly, whether or not it is a pointer.

Here is the summary:

  1. When declaring template parameters, form and typename are interchangeable.
  2. Use typename to identify nested dependent type names.
  3. If at that place is any doubtfulness as to whether typename is necessary to indicate that a name is a type, simply specify it. There is not damage in specifying typename before a blazon.

Instantiation

Template is not itself a class or a office, information technology is a blueprint. The compiler uses the template to generate blazon-specific versions. The process of generating a type-specific instance of a template is knows equally instantiation. A template is instantiated when we utilise it: A form template is instantiated when we refer to an bodily template form type, and a function template is instantiated when we call it or use information technology to initialize or assign to a pointer to function.

When we write

vector<double> vd;        

the compiler automatically creates a class named vector<double>. In effect, the compiler creates the vector<double> form by rewriting the vector template, replacing every occurrence of the template parameter T by the blazon double.

template<class T>  grade vector { 	int sz; 	T* element; public: 	vector():size(0), element(0) {} 	~vector() { delete[] element; } 	vector &operator;=(const vector&); 	const T& operator[](int n){ render element[n]; } 	void push_back(const T&d;);       ... };        

Template Specialization

It is not always possible to write a single template that is for every possible template argument with which the template might exist instantiated. Because general definition might not compile or might exercise the incorrect thing, we may be able to take advantage of some specific knowledge virtually a type to write a more efficient function than the 1 that is instantiated from the template.

If we want to implement dissimilar template when a specific type is passed as template parameter, we can declare a specialization of that template.

For example, let's suppose that we have a very simple form chosen DoublePlay that tin can store one element of whatsoever type and that it has just one member part chosen doubleIt, which doubles its value. Just we find that when information technology stores an chemical element of type string information technology would be more than convenient to have a completely different implementation with a function member mine, then we decide to declare a class template specialization for that type

#include <iostream> #include <string>  template <class T> form DoublePlay  { individual: 	T mine; public: 	DoublePlay(T& a): mine(a){} 	T doubleIt(){ 		return mine*2; 	} };          template <> class DoublePlay <std::string>                    { private: 	std::cord mine; public: 	DoublePlay(std::string& a):mine(a){} 	std::cord& doubleIt(){ 		render mine.append(mine); 	} };  int main()  { 	int i = ten; 	std::cord s="ab";  	DoublePlay<int>myPlayA(i); 	DoublePlay<std::string>myPlayB(s);  	std::cout << i << " to " << myPlayA.doubleIt() << std::endl; 	std::cout << southward << " to " << myPlayB.doubleIt() << std::endl; }        

Output is:

10 to 20 ab to abab        

The syntax we used in the form template specialization:

template <> class mycontainer <std::string> { ... };        

Notice that we precede the class template name with an empty template<> parameter list. This is to explicitly declare it as a template specialization. In other words, the template<> in declarations distinguish betwixt the explicit instantiation and the explicit specialization.

Actually, to extend our understanding of templates, we need to investigate the term instantiation and specialization. Including a function template in our code does not in itself generate a role definition. Information technology's only a programme for generating a function definition. When the compiler uses the template to generate a function definition for a detail blazon, the issue is termed an instantiation of the template.

In our earlier instance, the part call min(x,y) causes the compiler to generate an instantiation of min(), using int as a blazon.

The template is non a function definition, just the specific instantiation using int is a office definition. This type of instantiation is termed implicit instantiation because the compiler deduces the necessity for making the definition past noting that the code uses a min() function with int parameters.

Merely more important than this prefix, is the <std::cord> specialization parameter after the course template name. This specialization parameter itself identifies the type for which nosotros are going to declare a template class specialization (string). Notice the differences between the generic grade template and the specialization:

template <class T> class DoublePlay { ... }; template <> course DoublePlay <string> { ... };        

The first line is the generic template, and the second one is the specialization.

When we declare specializations for a template class, we must besides define all its members, even those exactly equal to the generic template grade, because there is no inheritance of members from the generic template to the specialization.

Here is some other example of using template specialization used as a office of template metaprogramming.

#include <iostream>  template <int n> struct Factorial  {     enum { value = n * Factorial<north - 1>::value }; };   template <> struct Factorial<0>  {     enum { value = 1 }; };   int chief() { 	std::cout << "Factorial<five>::value = " << Factorial<v>::value << std::endl;	 }        

Templates Is A Runtime Mechanism In Cpp?,

Source: https://www.bogotobogo.com/cplusplus/templates.php

Posted by: alexanderthowas1943.blogspot.com

0 Response to "Templates Is A Runtime Mechanism In Cpp?"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel