第1章 函数模板:1.1 初探函数模板

Chapter 1: Function Templates

第1章 函数模板

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

本章介绍函数模板。函数模板是一些被参数化的函数,它们代表的是一个函数家族

1.1 A First Look at Function Templates

1.1 初探函数模板

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.

函数模板提供了一种函数行为,该行为可以用多种不同类型进行调用。也就是说,函数模板代表一个函数家族。它的表示形式看起来和普通函数很相似,唯一区别的就是该函数的某些元素尚未确定:这些元素将在使用时被参数化。为了说明这点,让我们先看一个简单的例子。

1.1.1 Defining the Template

1.1.1 定义模板

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

下面是一个返回两个值中最大者的函数模板:

template<typename T>
T max (T a, T b)
{
    // if b < a then yield a else yield b(如果b<a,返回a否则返回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:

这个模板定义了一个“返回两个值中最大者”的函数家族。这两个值是通过函数参数a和b传递给函数模板的。而参数的类型还没确定,用模板参数T来替代。如例子所示,模板参数必须用如下形式的语法来声明:

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).

在我们这个例子里,参数列表是typename T。可以看到:我们用小于号和大于号来组成参数列表外部的一对括号,并把它们称作尖括号。关键字typename引入了所谓的类型参数T,到目前为止,它是C++程序使用最广泛的模板参数。也可以用其他的一些模板参数,我们将在后面介绍(见第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.

在上面程序中,类型参数是T。你可以使用任何标识符作为类型参数的名称,但使用T己经成为一个习惯。事实上,类型参数T表示的是,调用者调用这个函数时所指定的任意类型。你可以使用任何类型(基本类型、类等)来实例化该类型参数,只要该类型提供模板使用的操作就可以。在这个例子中,类型T需要支持operator<,因为a和b就是使用这个运算符来比较大小的。也许max()的定义中,最不明显的是T的类型必须是支持拷贝操作,因为函数是按值返回的。

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.

由于历史原因,你可能还会用class取代typename来定义类型参数。在C++98标准的发展过程中,关键字typename的出现相对较晚。在此之前,关键字class是引入类型参数的唯一方法,并一直作为有效方式保留下来。

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

因此,模板max()还可以有如下的等价定义:

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.

从语义上讲,这里的class和typename是等价的。因此,即使这里使用了class,你也可以用任何类型。但是,class的这种用法往往会造成误导(这里的class并不意味着只有类才能被用来替代T,事实上基本类型也可以)。因此,对于引入类型参数的用法,你应该尽量使用typename。此外,请注意,与类类型的声明不同,在声明模板参数类型时不能使用struct关键字来代替typename。

1.1.2 Using the Template

1.1.2 使用模板

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

下面的程序展示了如何使用max()函数模板:

#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:

在上面的程序里,max()被调用了3次:一次用两个int,一次用两个double,还有一次用了两个std::string。每一次都计算出两个实参数最大值。而调用的结果是产生如下的程序输出:

    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.

可以看到:max()模板每次调用的前面都加域限定符“::”,这是为了确保我们调用的是全局名称空间中的max()。因为标准库也有一个std::max()模板,在某些情况下也可能被调用到,因此有时会产生二义性。

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()

通常而言,并不是把模板编译成一个可处理任何类型的单一实体;相反,对于实例化模板参数的每种类型,都从模板产生出一个个不同的实体。因此,针对3种类型中的每一种,max()都被编译了一次。比如,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:

使用了以int作为模板参数T的函数模板。因此,它具有调用如下代码的语义:

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.

这种用具体类型代替模板参数的过程叫做实例化(instantiation)。它产生了一个模板的实例。

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.

可以看到:只要使用函数模板,编译器就会自动地引发这样一个实例化过程,因此程序员并不需要额外请求模板的实例化。

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

类似地,max()的其他调用也将为double和std::string灾例化max模板。就好像如下的代码是被单独地声明和实现了一样:

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:

还要注意,如果结果代码是有效的,则void是一个有效的模板参数,比如:

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

}

void* vp = nullptr;
foo(vp); // OK: deduces void(OK:推导为void)
foo(void*);

1.1.3 Two-Phase Translation

1.1.3 两个阶段编译

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:

如果试图实例化一个模板,而该模板内部使用了该类不支持的一些操作,将会导致一个编译期错误,例如:

std::complex<float> c1, c2;  // doesn’t provide operator <  (没提供operator<)
…
: :max(c1,c2); // ERROR at compile time(编译期错误)

Thus, templates are “compiled” in two phases:

于是,我们可以得出一个结论:模板被编译了两次,分别发生在:

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

实例化之前,先检查模板代码本身(但不会检查模板参数),查看语法是否正确。这包括:

    – Syntax errors are discovered, such as missing semicolons.

       在这里会发现语法错误,如遗漏分号

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

      发现使用未知的非依赖型名称(如类型名称或函数名称...)

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

      检查不依赖模板参数的静态断言

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:

在实例化期间,再次检查模板代码以确保所有代码有效。也就是说,现在特别是所有那些依赖模板参数的部分会进行两次检查。

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.

名称被两次检查的事实,也被称为两阶段查找。这将在第249页的14.3.1节中详细讨论。

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.

需要说明的是,一些编译器在第1阶段编译时并不执行完整的检查。因此,在模板至少被实例化一次之前,你可能看不到一些一般性的问题。

Compiling and Linking

编译和链接

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.

这给实际中模板处理带来了一个重要的问题: 当以引发模板实例化的方式使用模板时,编译器(在某个时刻)需要检查模板的定义。这不同于普通函数中的编译和链接的区别,因为对于普通函数而言,只要有该函数的声明(即不需要定义),就可以顺利通过编译。我们将在第9章中讨论这个问题的处理方法。目前,让我们采用最简单的方法: 在头文件中实现每个模板。

猜你喜欢

转载自www.cnblogs.com/5iedu/p/12696434.html