《C++ Primer第五版》读书笔记(14)--Templates and Generic Programming

Both object-oriented programming (OOP) and generic programming deal with types that are not known at the time the program is written. The distinction between the two is that OOP deals with types that are not known until run time, whereas in generic programming the types become known during compilation.

When we write a generic program, we write the code in a way that is independent of any particular type. When we use a generic program, we supply the type(s) or value(s) on which that instance of the program will operate.

Templates are the foundation of generic programming. A template is a blueprint or formula for creating classes or functions.We can use and have used templates without understanding how they are defined. In this chapter we’ll see how to define our own templates.

16.1 Defining a Template


16.1.1 Function Templates

A function template is a formula from which we can generate type-specific versions of that function.
template<typename T> int compare(const T &v1, const T &v2)
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}


A template definition starts with the keyword template followed by a template parameter list, which is a comma-separated list of one or more template parameters bracketed by the less-than (<) and greater-than (>) tokens. Ina template definition, the template parameter list cannot be empty.

When we use a template, we specify - either implicitly or explicitly - template argument(s) to bind to the template parameter(s).

Instantiating a Function Template


When we call a function template, the compiler (ordinarily) uses the arguments of the call to deduce(instantiate) the template argument(s) for us.
cout << compare(1, 0) << endl;  // T is int
// instantiates int compare(const vector<int>&, const vector<int>&)
vector<int> vec1{1, 2, 3}, vec2{4, 5, 6};
cout << compare(vec1, vec2) << endl; // T is vector<int>
For the first call, the compiler will write and compile a version of compare with T replaced by int:
int compare(const int &v1, const int &v2)
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
These compiler-generated functions are generally referred to as an instantiation of the template.


Template Type Parameters
Our compare function has one template type parameter. In general, we can use a type parameter as a type specifier in the same way that we use a built-in or class type specifier. In particular, a type parameter can be used to name the return type or a function parameter type, and for variable declarations or casts inside the function body:
// ok: same type used for the return type and parameter
template <typename T> T foo(T* p)
{
T tmp = *p; // tmp will have the type to which p points
// ...
return tmp;
}


Each type parameter must be preceded by the keyword class or typename:
// error: must precede U with either typename or class
template <typename T, U> T calc(const T&, const U&);
These keywords have the same meaning and can be used interchangeably inside a template parameter list. A template parameter list can use both keywords:
// ok: no distinction between typename and class in a template parameter list
template <typename T, class U> calc (const T&, const U&);
It may seem more intuitive to use the keyword typename rather than class to designate a template type parameter. After all, we can use built-in (nonclass) types as a template type argument. Moreover, typename more clearly indicates that the name that follows is a type name. However, typenamewas added to C++ after templates were already in widespread use; some programmers continue to use class exclusively.


Nontype Template Parameters
A nontype parameter represents a value rather than a type. When the template is instantiated, nontype parameters are replaced with a value supplied by the user or deduced by the compiler. These values must be constant expressions (§ 2.4.4, p. 65), which allows the compiler to instantiate the templates during compile time.
template<unsigned N, unsigned M>
int compare(const char (&p1)[N], const char (&p2)[M])
{
return strcmp(p1, p2);
}
when we call this version of compare: 
compare("hi", "mom")

the compiler will use the size of the literals to instantiate a version of the template with the sizes substituted for N and M. Remembering that the compiler inserts a null terminator at the end of a string literal, the compiler will instantiate:
int compare(const char (&p1)[3], const char (&p2)[4]);


A nontype parameter may be an integral type, or a pointer or (lvalue) reference to an object or to a function type.
An argument bound to a nontype integral parameter must be a constant expression. Arguments bound to a pointer or reference nontype parameter must have static lifetime.
A template nontype parameter is a constant value inside the template definition. A nontype parameter can be used when constant expressions are required, for example, to specify the size of an array.


inline and constexpr Function Templates
// ok: inline specifier follows the template parameter list
template <typename T> inline T min(const T&, const T&);
// error: incorrect placement of the inline specifier
inline template <typename T> T min(const T&, const T&);

Compilation Errors Are Mostly Reported during Instantiation
It is up to the caller to guarantee that the arguments passed to the template support any operations that template uses, and that those operations behave correctly in the context in which the template uses them.


16.1.2 Class Templates
A class template is a blueprint for generating classes. Class templates differ from function templates in that the compiler cannot deduce the template parameter type(s) for a class template. Instead, as we’ve seen many times, to use a class template we must supply additional information inside angle brackets following the template’s name . That extra information is the list of template arguments to use in place of the template parameters.

Defining a Class Template


template<typename T> class Blob {
public:
typedef T value_type;
typedef typename std::vector<T>::size_type size_type;
// constructors
Blob();
Blob(std::initializer_list<T> il);
// number of elements in the Blob
size_type size() const { return data->size(); }
bool empty() const { return data->empty(); }
// add and remove elements
void push_back(const T &t) {data->push_back(t);}
// move version; see § 13.6.3 (p. 548)
void push_back(T &&t) { data->push_back(std::move(t)); }
void pop_back();
// element access
T& back();
T& operator[](size_type i); // defined in § 14.5 (p. 566)
private:
std::shared_ptr<std::vector<T>> data;
// throws msg if data[i] isn‘t valid
void check(size_type i, const std::string &msg) const;
};



Instantiating a Class Template


When we use a class template, we must supply a list of explicit template arguments that are bound to the template’s parameters.
Blob<int> ia;  // empty Blob<int>
Blob<int> ia2 = {0,1,2,3,4}; // Blob<int> with five elements
From these definitions, the compiler will instantiate a class that is equivalent to
template<> class Blob<int> {
typedef typename std::vector<int>::size_type size_type;
Blob();
Blob(std::initializer_list<int> il);
// ...
int& operator[](size_type i);
private:
std::shared_ptr<std::vector<int>> data;
void check(size_type i, const std::string &msg) const;
};
Each instantiation of a class template constitutes an independent class. The type Blob<string>has no relationship to, or any special access to, the members of any other Blob type.

References to a Template Type in the Scope of the Template
In order to read template class code, it can be helpful to remember that the name of a class template is not the name of a type. A class template is used to instantiate a type, and an instantiated type always includes template argument(s).

Member Functions of Class Templates
ret-type StrBlob::member-name(parm-list)
the corresponding Blob member will look like
template<typename T> ret-typeBlob<T>::member-name(parm-list)
template<typename T> T& Blob<T>::back()
{
check(0, "back on empty Blob");
    return data->back();
}


Instantiation of Class-Template Member Functions
By default, a member function of a class template is instantiated only if the program uses that member function.


Simplifying Use of a Template Class Name inside Class Code
There is one exception to the rule that we must supply template arguments when we use a class template type. Inside the scope of the class template itself, we may use the name of the template without arguments. When we are inside the scope of a class template, the compiler treats references to the template itself as if we had supplied template arguments matching the template’s own parameters.


Class Templates and Friends
When a class contains a friend declaration (§ 7.2.1, p. 269), the class and the friend can independently be templates or not. A class template that has a nontemplate friend grants that friend access to all the instantiations of the template. When the friend is itself a template, the class granting friendship controls whether friendship includes all instantiations of the template or only specific instantiation(s).


One-to-One Friendship
The most common form of friendship from a class template to another template (class or function) establishes friendship between corresponding instantiations of the class and its friend.
In order to refer to a specific instantiation of a template (class or function) we must first declare the template itself. A template declaration includes the template’s template parameter list:
// forward declarations needed for friend declarations in Blob
template <typename> class BlobPtr;
template <typename> class Blob; // needed for parameters in operator==
template <typename T>
bool operator==(const Blob<T>&, const Blob<T>&);
template <typename T> class Blob {
// each instantiation of Blob grants access to the version of
// BlobPtr and the equality operator instantiated with the same type
friend class BlobPtr<T>;
friend bool operator==<T>(const Blob<T>&, const Blob<T>&);
// other members as in § 12.1.1 (p. 456)
};
The friend declarations use Blob’s template parameter as their own template argument. Thus, the friendship is restricted to those instantiations of BlobPtr and the equality operator that are instantiated with the same type:
Blob<char>ca; // BlobPtr<char> and operator==<char> are friends
Blob<int> ia;  // BlobPtr<int> and operator==<int> are friends


General and Specific Template Friendship
A class can also make every instantiation of another template its friend, or it may limit friendship to a specific instantiation:
// forward declaration necessary to befriend a specific instantiation of a template
template <typename T> class Pal;
class C
{  //  C is an ordinary, nontemplate class
friend class Pal<C>;  // Pal instantiated with class C is a friend to C
// all instances of Pal2 are friends to C;
// no forward declaration required when we befriend all instantiations
template <typename T> friend class Pal2;
};
template <typename T> class C2 { // C2 is itself a class template
// each instantiation of C2 has the same instance of Pal as a friend
friend class Pal<T>;  // a template declaration for Pal must be in scope
// all instances of Pal2 are friends of each instance of C2, prior declaration needed
template <typename X> friend class Pal2;
// Pal3 is a nontemplate class that is a friend of every instance of C2
friend class Pal3;  // prior declaration for Pal3 not needed
}
To allow all instantiations as friends, the friend declaration must use template parameter(s) that differ from those used by the class itself.

Befriending the Template’s Own Type Parameter
Under the new standard, we can make a template type parameter a friend:
template<typename Type> class Bar {
friend Type; // grants access to the type used to instantiate Bar
//  ...
};


Template Type Aliases
Because a template is not a type, we cannot define a typedefthat refers to a template. The new standard lets us define a type alias for a class template:
template<typenameT> using twin = pair<T, T>;
twin<string> authors; // authors is a pair<string, string>
Here we’ve defined twin as a synonym for pairs in which the members have the same type. Users of twin need to specify that type only once.
A template type alias is a synonym for a family of classes:
twin<int> win_loss;  // win_loss is a pair<int, int>
twin<double> area;  // area is a pair<double, double>
When we define a template type alias, we can fix one or more of the template parameters:
template<typename T> using partNo = pair<T, unsigned>;
partNo<string> books;  // books is a pair<string, unsigned>
partNo<Vehicle> cars;  // cars is a pair<Vehicle, unsigned>
partNo<Student> kids;  // kids is a pair<Student, unsigned>


static Members of Class Templates
Like any other class, a class template can declare static members.


16.1.3 Template Parameters

Template Declarations

A template declaration must include the template parameters :
// declares but does not define compare and Blob
template <typename T> int compare(const T&, const T&);
template <typename T> class Blob;


Using Class Members That Are Types
Assuming T is a template type parameter, When the compiler sees code such as T::mem it won’t know until instantiation time whether memis a type or a static data member.
By default, the language assumes that a name accessed through the scope operator is not a type. As a result, if we want to use a type member of a template type parameter, we must explicitly tell the compiler that the name is a type. We do so by using the keyword typename:
template <typename T> typename T::value_type  top(const T& c){ 
if (!c.empty())
   return c.back();
else
   return typename T::value_type();
}


When we want to inform the compiler that a name represents a type, we must use the keyword typename, not class.

Default Template Arguments
Under the new standard, we can supply default arguments for both function and class templates. Earlier versions of the language, allowed default arguments only with class templates.
Template Default Arguments and Class Templates
Whenever we use a class template, we must always follow the template’s name with brackets. The brackets indicate that a class must be instantiated from a template. In particular, if a class template provides default arguments for all of its template parameters, and we want to use those defaults, we must put an empty bracket pair following the template’s name:
template<class T = int> class Numbers {  // by default T is int
public:
Numbers(T v = 0): val(v) { }
// various operations on numbers
private:
T val;
};
Numbers<long double> lots_of_precision;
Numbers<> average_precision; // empty <> says we want the default type


16.1.4 Member Templates
A class—either an ordinary class or a class template—may have a member function that is itself a template. Such members are referred to as member  templates. Member templates may not be virtual.
Member Templates of Class Templates


16.1.5 Controlling Instantiations
The fact that instantiations are generated when a template is used (§ 16.1.1, p. 656) means that the same instantiation may appear in multiple object files. When two or more separately compiled source files use the same template with the same template arguments, there is an instantiation of that template in each of those files.
In large systems, the overhead of instantiating the same template in multiple files can become significant. Under the new standard, we can avoid this overhead through an explicit instantiation. An explicit instantiation has the form

extern template declaration;// instantiation declaration
template declaration;  // instantiation definition

// instantion declaration and definition
extern template class Blob<string>;  // declaration
template int compare(const int&, const int&);  // definition

Instantiation Definitions Instantiate All Members
An instantiation definition for a class template instantiates allthe members of that template including inline member functions. When the compiler sees an instantiation definition it cannot know which member functions the program uses. Hence, unlike the way it handles ordinary class template instantiations, the compiler instantiates allthe members of that class. Even if we do not use a member, that member will be instantiated. Consequently, we can use explicit instantiation only for types that can be used with all the members of that template.
An instantiation definition can be used only for types that can be used with every member function of a class template.


16.1.6 Efficiency and Flexibility
By binding the deleter at compile time, unique_ptr avoids the run-time cost of an indirect call to its deleter. By binding the deleter at run time, shared_ptr makes it easier for users to override the deleter.

16.2 Template Argument Deduction

The process of determining the template arguments from the function arguments is known as template argument deduction.


16.2.1 Conversions and Template Type Parameters
const conversions and array or function to pointer are the only automatic conversions for arguments to parameters with template types.Normal conversions are applied to arguments whose type is not a template parameter.

16.2.2 Function-Template Explicit Arguments
In some situations, it is not possible for the compiler to deduce the types of the template arguments. In others, we want to allow the user to control the template instantiation. Both cases arise most often when a function return type differs from any of those used in the parameter list.


// T1 cannot be deduced: it doesn‘t appear in the function parameter list
template <typename T1, typename T2, typename T3>
T1 sum(T2, T3);

In this case, there is no argument whose type can be used to deduce the type of T1. The caller must provide an explicit template argument for this parameter on each call to sum.
auto val3 = sum<long long>(i, lng); // long long sum(int, long)

This call explicitly specifies the type for T1. The compiler will deduce the types for T2 and T3 from the types of I and lng.
Explicit template argument(s) are matched to corresponding template parameter(s) from left to right:
// poor design: users must explicitly specify all three template parameters
template <typename T1, typename T2, typename T3> T3 alternative_sum(T2, T1);

16.2.3 Trailing Return Types and Type Transformation
// a trailing return lets us declare the return type after the parameter list is seen
template <typename It> auto fcn(It beg, It end) -> decltype(*beg)
{
// process the range
return *beg;  // return a reference to an element from the range
}
Here we’ve told the compiler that fcn’s return type is the same as the type returned by dereferencing its begparameter. The dereference operator returns an lvalue (§ 4.1.1, p. 136), so the type deduced by decltypeis a reference to the type of the element that beg denotes. Thus, if fcnis called on a sequence of strings, the return type will be string&. If the sequence is int, the return will be int&.


16.2.4 Function Pointers and Argument Deduction
We can use that pointer to point to an instantiation of compare:
template<typename T> int compare(const T&, const T&);
// pf1 points to the instantiation int compare(const int&, const int&)
int (*pf1)(const int&, const int&) = compare;

《C++ Primer第五版》读书笔记(14)--Templates and Generic Programming,布布扣,bubuko.com

《C++ Primer第五版》读书笔记(14)--Templates and Generic Programming

上一篇:《C++ Primer第五版》学习笔记-插播-模板偏特化、编译期Assertions与Type2Type


下一篇:python的数据结构