Chapter 1 Function Templates: 1.1 Preliminary Exploration of Function Templates

Chapter 1: Function Templates

Chapter 1 Function Template

This chapter introduces function templates. Function templates are functions that are parameterized so that they represent a family of functions.

This chapter introduces function templates. Function templates are some functions that are parameterized, they represent a family of functions

 

1.1 A First Look at Function Templates

1.1 Preliminary exploration of function templates

 

Function templates provide a functional behavior that can be called for different types. In other words, a function template represents a family of functions. The representation looks a lot like an ordinary function, except that some elements of the function are left undetermined: These elements are parameterized. To illustrate, let’s look at a simple example.

The function template provides a function behavior that can be called with many different types. In other words, the function template represents a family of functions. Its representation looks very similar to ordinary functions, the only difference is that some elements of the function have not yet been determined: these elements will be parameterized when used. To illustrate this, let us first look at a simple example.

 

1.1.1 Defining the Template

1.1.1 Define the template

 

The following is a function template that returns the maximum of two values:

Here is a function template that returns the largest of the two values:

template<typename T>
T max (T a, T b)
{
    // if b <a then yield a else yield b (if b <a, return a otherwise return b) 
    return b <a? a: b;
}

This template definition specifies a family of functions that return the maximum of two values, which are passed as function parameters a and b. The type of these parameters is left open as template parameter T. As seen in this example, template parameters must be announced with syntax of the following form:

This template defines a family of functions that "return the largest of two values". These two values ​​are passed to the function template through the function parameters a and b. The type of the parameter has not yet been determined, and the template parameter T is used instead. As shown in the example, template parameters must be declared with the following syntax:

template< comma-separated-list-of-parameters >

In our example, the list of parameters is typename T. Note how the < and > tokens are used as brackets; we refer to these as angle brackets. The keyword typename introduces a type parameter. This is by far the most common kind of template parameter in C++ programs, but other parameters are possible, and we discuss them later (see Chapter 3).

In our example, the parameter list is typename T. It can be seen that we use less than and greater than signs to form a pair of parentheses outside the parameter list, and call them angle brackets. The keyword typename introduces the so-called type parameter T, which is by far the most widely used template parameter in C ++ programs. You can also use some other template parameters, which we will introduce later (see Chapter 3)

 

Here, the type parameter is T. You can use any identifier as a parameter name, but using T is the convention. The type parameter represents an arbitrary type that is determined by the caller when the caller calls the function. You can use any type(fundamental type, class, and so on) as long as it provides the operations that the template uses. In this case, type T has to support operator < because a and b are compared using this operator. Perhaps less obvious from the definition of max() is that values of type T must also be copyable in order to be returned.

In the above program, the type parameter is T. You can use any identifier as the name of the type parameter, but using T has become a habit. In fact, the type parameter T represents any type specified by the caller when calling this function. You can use any type (basic type, class, etc.) to instantiate the type parameter, as long as the type provides the operation used by the template. In this example, the type T needs to support operator <, because a and b use this operator to compare the size. Perhaps the most obvious thing in the definition of max () is that the type of T must support copy operations because the function returns by value.

 

For historical reasons, you can also use the keyword class instead of typename to define a type parameter. The keyword typename came relatively late in the evolution of the C++98 standard. Prior to that, the keyword class was the only way to introduce a type parameter, and this remains a valid way to do so.

For historical reasons, you may also use class instead of typename to define type parameters. In the development of the C ++ 98 standard, the keyword typename appeared relatively late. Prior to this, the keyword class was the only way to introduce type parameters, and has been retained as an effective way.

 

Hence, the template max() could be defined equivalently as follows:

Therefore, the template max () can also have the following equivalent definitions:

template<class T>
T max (T a, T b)
{
    return b < a ? a : b;
}

Semantically there is no difference in this context. So, even if you use class here, any type may be used for template arguments. However, because this use of class can be misleading (not only class types can be substituted for T), you should prefer the use of typename in this context. However, note that unlike class type declarations, the keyword struct cannot be used in place of typename when declaring type parameters.

Semantically speaking, the class and typename here are equivalent. Therefore, even if class is used here, you can use any type. However, this usage of class is often misleading (class here does not mean that only class can be used to replace T, in fact, basic types can also). Therefore, for the usage of introducing type parameters, you should try to use typename. In addition, please note that unlike the declaration of class types, the struct keyword cannot be used in place of typename when declaring template parameter types.

 

1.1.2 Using the Template

1.1.2 Using templates

 

The following program shows how to use the max() function template:

The following program shows how to use the max () function template:

#include "max1.hpp"
#include <iostream>
#include <string>

int main ()
{
    int i = 42;
    std::cout << "max(7,i): " << ::max(7,i) << ’\n’;
    double f1 = 3.4; double f2 = -6.7;
    std::cout << "max(f1,f2): " << ::max(f1,f2) << ’\n’;
    std::string s1 = "mathematics";
    std::string s2 = "math";
    std::cout << "max(s1,s2): " << ::max(s1,s2) << ’\n’;
}

Inside the program, max() is called three times: once for two ints, once for two doubles, and once for two std::strings. Each time, the maximum is computed. As a result, the program has the following output:

In the above program, max () is called 3 times: two ints at a time, two doubles at a time, and two std :: string at a time. Each time, the maximum of two real parameters is calculated. The result of the call is the following program output:

    max(7,i): 42

    max(f1,f2): 3.4

    max(s1,s2): mathematics

 

Note that each call of the max() template is qualified with ::. This is to ensure that our max() template is found in the global namespace. There is also a std::max() template in the standard library, which under some circumstances may be called or may lead to ambiguity.

It can be seen that the max () template is prefixed with the domain qualifier "::" before each call, which is to ensure that we are calling max () in the global namespace. Because the standard library also has a std :: max () template, it may also be called in some cases, so it sometimes produces ambiguity.

 

Templates aren’t compiled into single entities that can handle any type. Instead, different entities are generated from the template for every type for which the template is used. Thus, max() is compiled for each of these three types. For example, the first call of max()

Generally speaking, the template is not compiled into a single entity that can handle any type; on the contrary, for each type of instantiated template parameters, a different entity is generated from the template. Therefore, for each of the three types, max () is compiled once. For example, the first call to max ():

int i = 42;
… max(7,i) …

uses the function template with int as template parameter T. Thus, it has the semantics of calling the following code:

A function template with int as the template parameter T is used. Therefore, it has the semantics of calling the following code:

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

 

The process of replacing template parameters by concrete types is called instantiation. It results in an instance of a template.

This process of replacing template parameters with specific types is called instantiation. It produces an instance of the template.

 

Note that the mere use of a function template can trigger such an instantiation process. There is no need for the programmer to request the instantiation separately.

It can be seen that as long as the function template is used, the compiler will automatically initiate such an instantiation process, so the programmer does not need to additionally request the instantiation of the template.

 

Similarly, the other calls of max() instantiate the max template for double and std::string as if they were declared and implemented individually:

Similarly, other calls to max () will also instantiate max templates for double and std :: string. It is as if the following code was declared and implemented separately:

double max (double, double);
std::string max (std::string, std::string);

Note also that void is a valid template argument provided the resulting code is valid. For example:

Also note that if the result code is valid, void is a valid template parameter, such as:

template<typename T>
T foo(T*)
{

}

void* vp = nullptr;
foo (vp); // OK: deduces void (OK: deduced as void) 
foo ( void *);

1.1.3 Two-Phase Translation

1.1.3 Two-stage compilation

 

An attempt to instantiate a template for a type that doesn’t support all the operations used within it will result in a compile-time error. For example:

If you try to instantiate a template, and the template uses some operations that are not supported by the class, it will cause a compile-time error, for example:

std::complex<float> c1, c2;  // doesn’t provide operator <  (没提供operator<)

:: max (c1, c2); // ERROR at compile time

Thus, templates are “compiled” in two phases:

So, we can draw a conclusion: the template was compiled twice, which occurred in:

 

1. Without instantiation at definition time, the template code itself is checked for correctness ignoring the template parameters. This includes:

Before instantiation, check the template code itself (but not the template parameters) to see if the syntax is correct. This includes:

    – Syntax errors are discovered, such as missing semicolons.

       You will find grammatical errors here, such as missing semicolons

   – Using unknown names (type names, function names, …) that don’t depend on template parameters are discovered.

      Found to use unknown, non-dependent type names (such as type names or function names ...)

   – Static assertions that don’t depend on template parameters are checked.

      Check for static assertions that do not depend on template parameters

 

2. At instantiation time, the template code is checked (again) to ensure that all code is valid. That is, now especially, all parts that depend on template parameters are double-checked.

For example:

During instantiation, check the template code again to ensure that all code is valid. In other words, all parts that depend on template parameters will now be checked twice.

 

For example:

for example:

template<typename T>
void foo(T t)
{
    undeclared(); // first-phase compile-time error if undeclared() unknown
    undeclared(t); // second-phase compile-time error if undeclared(T) unknown
    static_assert(sizeof(int) > 10, // always fails if sizeof(int)<=10
                     "int too small");
    static_assert(sizeof(T) > 10, //fails if instantiated for T with size <=10
                     "T too small");
}

The fact that names are checked twice is called two-phase lookup and discussed in detail in Section 14.3.1 on page 249.

The fact that the name is checked twice is also called a two-stage lookup. This will be discussed in detail in Section 14.3.1 on page 249.

 

Note that some compilers don’t perform the full checks of the first phase.6 So you might not see general problems until the template code is instantiated at least once.

It should be noted that some compilers do not perform a complete check when compiling in the first stage. Therefore, you may not see some general problems before the template is instantiated at least once.

 

Compiling and Linking

Compile and link

 

Two-phase translation leads to an important problem in the handling of templates in practice: When a function template is used in a way that triggers its instantiation, a compiler will (at some point) need to see that template’s definition. This breaks the usual compile and link distinction for ordinary functions, when the declaration of a function is sufficient to compile its use. Methods of handling this problem are discussed in Chapter 9. For the moment, let’s take the simplest approach: Implement each template inside a header file.

This brings an important problem to template processing in practice: when using a template in a way that causes template instantiation, the compiler (at some point) needs to check the template definition. This is different from the difference between compilation and linking in ordinary functions, because for ordinary functions, as long as there is a declaration of the function (that is, no definition is required), it can be successfully compiled. We will discuss how to deal with this problem in Chapter 9. For now, let's take the simplest method: implement each template in the header file.

Guess you like

Origin www.cnblogs.com/5iedu/p/12696434.html