Chapter.16 Templates And Generic Programming

Introduction

    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.

    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 for generic programming in C++. A template is a blueprint or formula for creating classes or functions. When we use a generic type,such as vector, or a generic function, such as find, we supply the information needed to transform that blueprint into a specific class or function. That transformation happens during compilation.


16.1.1 Defining a Template

 The template version of compare looks like:
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 templateparameter list, which is a comma-separated list of one or more template parameters bracketed by the less-than (<) and greater-than (>) tokens.

note:In a template definition, the template parameter list cannot be empty.

Instantiating a Function Template (complier-generated)
    The compiler uses the deduced template parameter(s) to instantiate a specific version of the function for us. When the compiler instantiates a template, it creates a new “instance” of the template using the actual template argument(s) in place of the corresponding template parameter(s)
    
note: Template arguments used for nontype template parameters must be constant
expressions.

template<unsigned N, unsigned M>
int compare(const char (&p1)[N], const char (&p2)[M])
{
       return strcmp(p1, p2);
}

note: Template programs should try to minimize the number of requirements
placed on the argument types.

Template Compilation
    Ordinarily, when we call a function, the compiler needs to see only a declaration for the function. Similarly, when we use objects of class type, the class definition must be available, but the definitions of the member functions need not be present. As a result, we put class definitions and function declarations in header files and definitions
of ordinary and class-member functions in source files
   Templates are different: To generate an instantiation, the compiler needs to have the code that defines a function template or class template member function.As a result, unlike nontemplate code, headers for templates typically include definitions as well as declarations


Warning: 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.

Exercise 16.4: Write a template that acts like the library find algorithm.The function will need two template type parameters, one to represent the function’s iterator parameters and the other for the type of the value. Use your function to find a given value in a vector<int> and in a list<string>:
template<class InputIt, class T>
InputIt find(InputIt first, InputIt last, const T& value)
{
    for (; first != last; ++first) {
        if (*first == value) {
            return first;
        }
    }
    return last;
}
Exercise 16.5: Write a template version of the print function from § 6.2.4
(p. 217) that takes a reference to an array and can handle arrays of any size
and any element type.
template<typename T,unsigned N>// N must be constexpr
unsigned print(T (&arr)[N])
{
    //by using array index to print
    for(int i = 0;i<N;++i)
        cout<<arr[i]<<endl;

    // by using the reference to array to print
    for(T &it : arr)
        cout<<it<<endl;


    //above can update the element value of array
    return N;  // return the size of a given array
}
Exercise 16.6: How do you think the library begin and end functions that take an array argument work? Define your own versions of these functions.
//the same as std::begin
template<typename T,unsigned size>
T* begin_def(T (&arr)[size])
{
    return arr;
}


// the same as std::end
//this should not be const
template<typename T,unsigned size>
T* end_def(T (&arr)[size])
{
    return arr+size;
}


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 (§ 3.3, p. 97). That extra information is the list of template arguments to use in place of the template parameters.
Defining a Class Template
     As an example, we’ll implement a template version of StrBlob (§ 12.1.1, p. 456). We’ll name our template Blob to indicate that it is no longer specific to strings.  As with the library containers, our users will have to specify the element type when they use a Blob we use the template parameters as stand-ins for types or values that will be supplied when the template is used.
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
   
Blob<int> ia; // empty Blob<int>
Blob<int> ia2 = {0,1,2,3,4}; // Blob<int> with five elements

note: 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
   
std::shared_ptr<std::vector<T>> data;
shared_ptr<vector<string>>

Member Functions of Class Templates
     A class template member function is itself an ordinary function. However, each instantiation of the class template has its own version of each member. As a result, a member function of a class template has the same template parameters as the class itself. Therefore, a member function defined outside the class template body starts with the keyword template followed by the class’ template parameter list
  
ret-type StrBlob::member-name(parm-list)
the corresponding Blob member will look like
template <typename T>
ret-type Blob<T>::member-name(parm-list)

Instantiation of Class-Template Member Functions
  note: By default, a member of an instantiated class template is instantiated only if the member is used.

Simplifying Use of a Template Class Name inside Class Code
     Inside the scope of the class template itself, we may use the name of the template without arguments
Using a Class Template Name outside the Class Template Body
 
template <typename T>
BlobPtr<T> BlobPtr<T>::operator++(int)
{
// no check needed here; the call to prefix increment will do the check
BlobPtr ret = *this; // save the current value
++*this; // advance one element; prefix ++ checks the increment
return ret; // return the saved state
}

Because the return type appears outside the scope of the class, we must specify thatthe return type returns a BlobPtr instantiated with the same type as the class. Inside the function body, we are in the scope of the class so do not need to repeat the template argument when we define ret.

note:Inside the scope of a class template, we may refer to the template without specifying template argument(s).


Class Templates and Friends
    One-to-One Friendship
// 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)
};
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

// 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
};

Template Type Aliases
    Because a template is not a type,we cannot define a typedef that refers to a template. That is, there is no way to define a typedef that refers to Blob<T>.
However, the new standard lets us define a type alias for a class template:
template<typename T> using twin = pair<T, T>;
twin<string> authors; // authors is a pair<string, string
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>
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


template <typename T> class Foo {
public:
static std::size_t count() { return ctr; }
// other interface members
private:
static std::size_t ctr;
// other implementation members
};

Each instantiation of Foo has its own instance of the static members. That is, for any given type X, there is one Foo<X>::ctr and one Foo<X>::count member.
     
// instantiates static members Foo<string>::ctr and Foo<string>::count
Foo<string> fs;
// all three objects share the same Foo<int>::ctr and Foo<int>::count members
Foo<int> fi, fi2, fi3;


As with any other static data member, there must be exactly one definition ofeach static data member of a template class. However, there is a distinct object for each instantiation of a class template. As a result, we define a static data member as a template similarly to how we define the member functions of that template:
template <typename T>
size_t Foo<T>::ctr = 0; // define and initialize ctr

As with static members of nontemplate classes, we can access a static member of a class template through an object of the class type or by using the scope operator to access the member directly. Of course, to use a static member through the class, we must refer to a specific instantiation:
Foo<int> fi; // instantiates Foo<int> class
// and the static data member ctr
auto ct = Foo<int>::count(); // instantiates Foo<int>::count
ct = fi.count(); // uses Foo<int>::count
ct = Foo::count(); // error: which template instantiation?
Like any other member function, a static member function is instantiated only if it is
used in a program.


16.1.3. Template Parameters

Template parameters follow normal scoping rules. The name of a template parameter can be used after it has been declared and until the end of the template declaration or definition. As with any other name, a template parameter hides any declaration of that name in an outer scope. Unlike most other contexts, however, a name used as a template parameter may not be reused within the template:
typedef double A;
template <typename A, typename B> void f(A a, B b)
{
A tmp = a; // tmp has same type as the template parameter A, not double
double B; // error: redeclares template parameter B
}

// error: illegal reuse of template parameter name V
template <typename V, typename V> // ...

template declarations
As with function parameters, the names of a template parameter need not be the same across the declaration(s) and the definition of the same template:

// all three uses of calc refer to the same function template
template <typename T> T calc(const T&, const T&); // declaration
template <typename U> U calc(const U&, const U&); // declaration
// definition of the template
template <typename Type>
Type calc(const Type& a, const Type& b) { /* . . . */ }

Best practices :For reasons we’ll explain in § 16.3 (p. 698), declarations for all the templates needed by a given file usually should appear together at the beginning of a file before any code that uses those names.



Using Class Members That Are Types
Recall that we use the scope operator (::) to access both static members and type members (§ 7.4, p. 282, and § 7.6, p. 301). 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();
}

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


Default Template Arguments


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


// Exercise 16.17:
// What, if any, are the differences between a type parameter that is declared
// as a typename and one that is declared as a class? When must typename be used?
//
//  There is no difference. typename and class are interchangeable in the
//  declaration of a type template parameter.
//  You do, however, have to use class (and not typename) when declaring a
//  template template parameter.
//
//  When we want to inform the compiler that a name represents a type, we must use
//  the keyword typename, not class
//

// Exercise 16.19:
// Write a function that takes a reference to a container and prints the
// elements in that container. Use the container’s size_type and size members
// to control the loop that prints the elements.
//
// Exercise 16.20:
// Rewrite the function from the previous exercise to use iterators returned
// from begin and end to control the loop.
//

#include <iostream>
#include <vector>
#include <list>

// ex16.19
template<typename Container>
std::ostream& print(Container const& container, std::ostream& os)
{
    for(typename Container::size_type i = 0; i != container.size(); ++ i)
        os << container[i] << " ";
    return os;
}

// ex16.20
template<typename Container>
std::ostream& print2(Container const& container, std::ostream &os)
{
    for(auto curr = container.cbegin(); curr != container.cend(); ++curr)
        os << *curr << " ";
    return os;
}


int main()
{
    std::vector<int> v = { 1, 23, 6, 4, 5, 7, 4 };
    std::list<std::string> l = { "ss", "sszz", "saaas", "s333s", "ss2"," sss" };
    print2(v, std::cout) << std::endl;
    print2(l, std::cout) << std::endl;

    return 0;
}


16.1.4 Member Templates

Member Templates of Ordianary (Nontemplate) Classes
// function-object class that calls delete on a given pointer
class DebugDelete {
public:
DebugDelete(std::ostream &s = std::cerr): os(s) { }
// as with any function template, the type of T is deduced by the compiler
template <typename T> void operator()(T *p) const
{ os << "deleting unique_ptr" << std::endl; delete p;
}
private:
std::ostream &os;
};

double* p = new double;
DebugDelete d; // an object that can act like a delete expression
d(p); // calls DebugDelete::operator()(double*), which deletes p
int* ip = new int;
// calls operator()(int*) on a temporary DebugDelete object
DebugDelete()(ip);

Because calling a DebugDelete object deletes its given pointer, we can also use DebugDelete as the deleter of a unique_ptr. To override the deleter of a unique_ptr, we supply the type of the deleter inside brackets and supply an object of the deleter type to the constructor (§ 12.1.5, p. 471):
// destroying the the object to which p points
// instantiates DebugDelete::operator()<int>(int *)
unique_ptr<int, DebugDelete> p(new int, DebugDelete());
// destroying the the object to which sp points
// instantiates DebugDelete::operator()<string>(string*)
unique_ptr<string, DebugDelete> sp(new string,
DebugDelete());
The unique_ptr destructor calls the DebugDelete’s call operator. Thus, whenever unique_ptr’s destructor is instantiated, DebugDelete’s call operator will also be instantiated: Thus, the definitions above will instantiate:
// sample instantiations for member templates of DebugDelete
void DebugDelete::operator()(int *p) const { delete p; }
void DebugDelete::operator()(string *p) const { delete p; }

Member Templates of Class Templates
we’ll give our Blob class a constructor that will take two iterators denoting a range of elements to copy. Because we’d like to support iterators into varying kinds of sequences, we’ll make this constructor a template
template <typename T> class Blob {
template <typename It> Blob(It b, It e);
// ...
};
Instantiation and Member Templates
int ia[] = {0,1,2,3,4,5,6,7,8,9};
C++ Primer, Fifth Edition
vector<long> vi = {0,1,2,3,4,5,6,7,8,9};
list<const char*> w = {"now", "is", "the", "time"};
// instantiates the Blob<int> class
// and the Blob<int> constructor that has two int* parameters
Blob<int> a1(begin(ia), end(ia));
// instantiates the Blob<int> constructor that has
// two vector<long>::iterator parameters
Blob<int> a2(vi.begin(), vi.end());
// instantiates the Blob<string> class and the Blob<string>
// constructor that has two (list<const char*>::iterator parameters
Blob<string> a3(w.begin(), w.end());



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。

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

When the compiler sees an extern template declaration, it will not generate code for that instantiation in that file. Declaring an instantiation as extern is a promise that there will be a nonextern use of that instantiation elsewhere in the program There may be several extern declarations for a given instantiation but there must be exactly one definition for that instantiation.

// Application.cc
// these template types must be instantiated elsewhere in the program
extern template class Blob<string>;
extern template int compare(const int&, const int&);
Blob<string> sa1, sa2; // instantiation will appear elsewhere
// Blob<int> and its initializer_list constructor instantiated in this file
Blob<int> a1 = {0,1,2,3,4,5,6,7,8,9};
Blob<int> a2(a1); // copy constructor instantiated in this file
int i = compare(a1[0], a2[0]); // instantiation will appear elsewhere
The file Application.o will contain instantiations for Blob<int>, along with the initializer_list and copy constructors for that class. The compare<int>function and Blob<string> class will not be instantiated in that file. There must be definitions of these templates in some other file in the program:

warning: There must be an explicit instantiation definition somewhere in the program for every instantiation declaration.


Instantiation Definitions Instantiate All Members

note : An instantiation definition can be used only for types that can be used with every member function of a class template.


Binding the Deleter at Run Time

Binding the Deleter at Compile Time

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. During template argument deduction, the compiler uses types of the arguments in the call to find the template arguments that generate a version of the function that best matches the given call.

16.2.1. Conversions and Template Type Parameters

As usual, top-level consts (§ 2.4.3, p. 63) in either the parameter or the argument are ignored. The only other conversions performed in a call to a function template are

const conversions: A function parameter that is a reference (or pointer) to a const can be passed a reference (or pointer) to a nonconst object (§ 4.11.2, p. 162).
Array- or function-to-pointer conversions: If the function parameter is not a reference type, then the normal pointer conversion will be applied to arguments of array or function type. An array argument will be converted to a pointer to its first element. Similarly, a function argument will be converted to a pointer to the
function’s type (§ 4.11.2, p. 161).
Other conversions, such as the arithmetic conversions (§ 4.11.1, p. 159), derived-to-base (§ 15.2.2, p. 597), and user-defined conversions (§ 7.5.4, p. 294, and § 14.9, p.579), are not performed.

template <typename T> T fobj(T, T); // arguments are copied
template <typename T> T fref(const T&, const T&); // references
string s1("a value");
const string s2("another value");
fobj(s1, s2); // calls fobj(string, string); const is ignored
fref(s1, s2); // calls fref(const string&, const string&)
// uses premissible conversion to const on s1
int a[10], b[42];
fobj(a, b); // calls f(int*, int*)
fref(a, b); // error: array types don't matc
 Note:const conversions and array or function to pointer are the only automatic conversions for arguments to parameters with template types.
 

Note:Normal conversions are applied to arguments whose type is not a template parameter.

// Exercise 16.32:
// What happens during template argument deduction?
//  The process of determining the template arguments from the function arguments
//  is known as template argument deduction. During template argument deduction,
//  the compiler uses types of the arguments in the call to find the template
//  arguments that generate a version of the function that best matches the given
//  call.


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

// T1 is explicitly specified; T2 and T3 are inferred from the argument types
auto val3 = sum<long long>(i, lng); // long long sum(int, long)
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);

Normal Conversions Apply for Explicitly Specified Arguments
For the same reasons that normal conversions are permitted for parameters that are defined using ordinary types (§ 16.2.1, p. 680), normal conversions also apply for arguments whose template type parameter is explicitly specified


16.2.3. Trailing Return Types and Type Transformation

return type is lvalue:
// 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
}
return type is object: ( we can use remove_reference to obtain the element type. The remove_reference template has one template type parameter and a (public) type member named type. If we instantiate remove_reference with a reference type, then type will be the referred-to type. For example, if we instantiate remove_reference<int&>, the type member will be in int.)
template <typename It>
auto fcn2(It beg, It end) ->
typename remove_reference<decltype(*beg)>::type
{
    // process the range
    return *beg; // return a copy of an element from the range
}
Exercise 16.41: Write a version of sum with a return type that is guaranteed to be large enough to hold the result of the addition.
template<typename T>
auto sum(T lhs, T rhs) -> decltype( lhs + rhs)
{
    return lhs + rhs;
}

16.2.4. Function Pointers and Argument Deduction


Note : When the address of a function-template instantiation is taken, the context

must be such that it allows a unique type or value to be determined for each
template parameter.


16.2.5. Template Argument Deduction and References

it is important to keep in mind two points: Normal reference binding rules apply; and consts are low level, not top level.


Type Deduction from Lvalue Reference Function Parameters

When a function parameter is an ordinary (lvalue) reference to a template type parameter (i.e., that has the form T&), the binding rules say that we can pass only an lvalue (e.g., a variable or an expression that returns a reference type). That argument might or might not have a const type. If the argument is const, then T will be deduced as a const type:
template <typename T> void f1(T&); // argument must be an lvalue
// calls to f1 use the referred-to type of the argument as the template parameter type
f1(i); // i is an int; template parameter T is int
f1(ci); // ci is a const int; template parameter T is const int
f1(5); // error: argument to a & parameter must be an lvalue

If a function parameter has type const T&, normal binding rules say that we can pass any kind of argument—an object (const or otherwise), a temporary, or a literal value.

template <typename T> void f2(const T&); // can take an rvalue
// parameter in f2 is const &; const in the argument is irrelevant
// in each of these three calls, f2's function parameter is inferred as const int&
f2(i); // i is an int; template parameter T is int
f2(ci); // ci is a const int, but template parameter T is int
f2(5); // a const & parameter can be bound to an rvalue; T is int

Type Deduction from Rvalue Reference Function Parameters
When a function parameter is an rvalue reference (§ 13.6.1, p. 532) (i.e., has the form T&&), normal binding rules say that we can pass an rvalue to this parameter.
template <typename T> void f3(T&&);
f3(42); // argument is an rvalue of type int; template parameter T is int

Reference Collapsing and Rvalue Reference Parameters
Assuming i is an int object, we might think that a call such as f3(i) would be illegal. After all, i is an lvalue, and normally we cannot bind an rvalue reference to an lvalue. However, the language defines two exceptions to normal binding rules that allow this kind of usage. These exceptions are the foundation for how library facilities such as move operate.
    The first exception affects how type deduction is done for rvalue reference parameters.When we pass an lvalue (e.g., i) to a function parameter that is an rvalue reference to a template type parameter (e.g, T&&),the compiler deduces the template type parameter as the argument’s lvalue reference type. So, when we call f3(i), the compiler deduces the type of T as int&, not int. Deducing T as int& would seem to mean that f3’s function parameter would be an rvalue reference to the type int&. Ordinarily, we cannot (directly) define a reference to a reference (§ 2.3.1, p. 51). However, it is possible to do so indirectly through a type alias (§ 2.5.1, p. 67) or through a template type parameter.
    we see the second exception to the normal binding rules: If we indirectly create a reference to a reference, then those references “collapse.” In all but one case, the references collapse to form an ordinary lvalue reference type. The new standard, expanded the collapsing rules to include rvalue references. References collapse to form an rvalue reference only in the specific case of an rvalue reference to an rvalue reference. That is, for a given type X:
• X& &, X& &&, and X&& & all collapse to type X&
• The type X&& && collapses to X&&

note: Reference collapsing applies only when a reference to a reference is created indirectly, such as in a type alias or a template parameter.
f3(i); // argument is an lvalue; template parameter T is int&
f3(ci); // argument is an lvalue; template parameter T is const int&

// invalid code, for illustration purposes only
void f3<int&>(int& &&); // when T is int&, function parameter is int& &&
The function parameter in f3 is T&& and T is int&, so T&& is int& &&, which
collapses to int&. Thus, even though the form of the function parameter in f3 is an
rvalue reference (i.e., T&&), this call instantiates f3 with an lvalue reference type
(i.e., int&):
void f3<int&>(int&); // when T is int&, function parameter collapses to int&

As a rusult. There are two important consequences from these rules:
• A function parameter that is an rvalue reference to a template type parameter (e.g., T&&) can be bound to an lvalue; and
• If the argument is an lvalue, then the deduced template argument type will be an lvalue reference type and the function parameter will be instantiated as an (ordinary) lvalue reference parameter (T&)


note:An argument of any type can be passed to a function parameter that is an
rvalue reference to a template parameter type (i.e., T&&). When an lvalue is
passed to such a parameter, the function parameter is instantiated as an
ordinary, lvalue reference (T&). 


Writing Template Functions with Rvalue Reference Parameters
template <typename T> void f3(T&& val)
{
    T t = val; // copy or binding a reference?
    t = fcn(t); // does the assignment change only t or val and t?
    if (val == t) { /* ... */ } // always true if T is a reference type
}
It is surprisingly hard to write code that is correct when the types involved might be plain (nonreference) types or reference types. In practice, rvalue reference parameters are used in one of two contexts: Either the template is forwarding its arguments, or the template is overloaded. We’ll look at forwarding in § 16.2.7 (p. 692) and at template overloading in § 16.3 (p. 694).

// Exercise 16.42:
// Determine the type of T and of val in each of the following calls:
//     template <typename T> void g(T&& val);
//     int i = 0; const int ci = i;
//     (a) g(i);
//  since i is lvalue, T is deduced as int&, val is int& && collapsing to int&
//     (b) g(ci);
//  since ci is lvaue, T is deduced as const int&, val is const int& && collapsing to const int&
//     (c) g(i * ci);
//  since i * ci is rvalue, T is deduced as int, val is int&& && colapsing to int&&
Exercise 16.44:
// Using the same three calls as in the first exercise, determine the types for T
// if g’s function parameter is declared as T (not T&&).
//                                           ^
//      g(i);       --  T is deduced as int
//      g(ci);      --  T is deduced as int, const is ignored.
//      g(i * ci);  --  T is deduced as int, (i * ci) returns rvalue which is copied to
//                      T
// What if g’s function parameter is const T&?
//                                    ^^^^^^^^
//      g(i)        --  T is deduced as int  , val : const int&
//      g(ci)       --  T is deduced as int  , val : const int&
//      g(i * ci)   --  T is deduced as int  , val : const int&(see example on page 687)
//



16.2.6. Understanding std::move

How std::move Is Defined
// for the use of typename in the return type and the cast see § 16.1.3 (p. 670)
// remove_reference is covered in § 16.2.3 (p. 684)
template <typename T>
typename remove_reference<T>::type&& move(T&& t)
{
    // static_cast covered in § 4.11.3 (p. 163)
    return static_cast<typename remove_reference<T>::type&&>(t);
}


16.2.7. Forwarding

note : A function parameter that is an rvalue reference to a template type
parameter (i.e., T&&) preserves the constness and lvalue/rvalue property of
its corresponding argument.

Using std::forward to Preserve Type Information in a Call

We can use a new library facility named forward to pass flip2’s parameters in a way that preserves the types of the original arguments. Like move, forward is defined in the utility header. Unlike move, forward must be called with an explicit template argument (§ 16.2.2, p. 682). forward returns an rvalue reference to that explicit argument type. That is, the return type of forward<T> is T&&.
note : When used with a function parameter that is an rvalue reference to template type parameter (T&&), forward preserves all the details about an argument’s type.
template<typename F,typename T1,typename T2>
void flip(F f,T1&& t1,T2&& t2)
{
    f(std::forward<T2>(t2),std::forward<T1>(t1));
}

16.3. Overloading and Templates

Function templates can be overloaded by other templates or by ordinary, nontemplate functions. As usual, functions with the same name must differ either as to the number or the type(s) of their parameters.
Function matching (§ 6.4, p. 233) is affected by the presence of function templates in the following ways:
• The candidate functions for a call include any function-template instantiation for which template argument deduction (§ 16.2, p. 678) succeeds.
• The candidate function templates are always viable, because template argument deduction will have eliminated any templates that are not viable.
• As usual, the viable functions (template and nontemplate) are ranked by the conversions, if any, needed to make the call. Of course, the conversions used to call a function template are quite limited (§ 16.2.1, p. 679).
• Also as usual, if exactly one function provides a better match than any of the others, that function is selected. However, if there are several functions that provide an equally good match, then:
     – If there is only one nontemplate function in the set of equally good matches, the nontemplate function          is called.
    – If there are no nontemplate functions in the set, but there are multiple function templates, and one of            these templates is more specialized than any of the others, the more specialized function template is            called.
    – Otherwise, the call is ambiguous.

Warning: Correctly defining a set of overloaded function templates requires a good understanding of the relationship among types and of the restricted conversions applied to arguments in template functions. 
//
// Exercise 16.48:
// Write your own versions of the debug_rep functions.
//

#include <iostream>
#include <memory>
#include <sstream>


// always declare first:

template <typename T> std::string debug_rep(const T& t);
template <typename T> std::string debug_rep(T* p);

std::string debug_rep(const std::string &s);
std::string debug_rep(char* p);
std::string debug_rep(const char *p);




// print any type we don't otherwise.
template<typename T> std::string debug_rep(const T& t)
{
    std::ostringstream ret;
    ret << t;
    return ret.str();
}

// print pointers as their pointer value, followed by the object to which the pointer points
template<typename T> std::string debug_rep(T* p)
{
    std::ostringstream ret;
    ret << "pointer: " << p;

    if(p)
        ret << " " << debug_rep(*p);
    else
        ret << " null pointer";

    return ret.str();
}

// non-template version
std::string debug_rep(const std::string &s)
{
    return '"' + s + '"';
}

// convert the character pointers to string and call the string version of debug_rep
std::string debug_rep(char *p)
{
    return debug_rep(std::string(p));
}

std::string debug_rep(const char *p)
{
    return debug_rep(std::string(p));
}

int main()
{

}

// Exercise 16.49:
// Explain what happens in each of the following calls:
//
// Exercise 16.50:
// Define the functions from the previous exercise so that they print an
// identifying message. Run the code from that exercise. If the calls behave
// differently from what you expected, make sure you understand why.
//

#include <iostream>
#include <memory>
#include <sstream>

template <typename T> void f(T)
{
    std::cout << "f(T)\n";
}

template <typename T> void f(const T*)
{
    std::cout << "f(const T*)\n";
}
template <typename T> void g(T)
{
    std::cout << "template <typename T> void g(T)\n";
}
template <typename T> void g(T*)
{
    std::cout << "template <typename T> void g(T*)\n";
}



int main()
{
    int i = 42, *p = &i;
    const int ci = 0, *p2 = &ci;

    //g(42);    //template <typename T> void g(T ); --is called
    //g(p);     //template <typename T> void g(T*); --is called
    //g(ci);      //template <typename T> void g(T)   --is called
    //g(p2);      //template <typename T> void g(T*)    --is called
    //f(42);    //f(T)
    //f(p);     //f(T)
    //f(ci);    //f(T)
    f(p2);      //f(const T*)

}

16.4. Variadic Templates

A variadic template is a template function or class that can take a varying number of parameters. The varying parameters are known as a parameter pack. There are two kinds of parameter packs: A template parameter pack represents zero or more template parameters, and a function parameter pack represents zero or more function parameters.
// Args is a template parameter pack; rest is a function parameter pack
// Args represents zero or more template type parameters
// rest represents zero or more function parameters
template <typename T, typename... Args>
void foo(const T &t, const Args& ... rest);

16.4.1. Writing a Variadic Function Template

// function to end the recursion and print the last element
// this function must be declared before the variadic version of print is defined
template<typename T>
ostream &print(ostream &os, const T &t)
{
return os << t; // no separator after the last element in the pack
}
// this version of print will be called for all but the last element in the pack
template <typename T, typename... Args>
ostream &print(ostream &os, const T &t, const Args&... rest)
{
   os << t << ", "; // print the first argument
   return print(os, rest...); // recursive call; print the other arguments
}

16.4.2. Pack Expansion

   Aside from taking its size, the only other thing we can do with a parameter pack is to expand it. When we expand a pack, we also provide a pattern to be used on each expanded element. Expanding a pack separates the pack into its constituent elements, applying the pattern to each element as it does so. We trigger an expansion by putting an ellipsis (. . . ) to the right of the pattern.
   
// call debug_rep on each argument in the call to print
template <typename... Args>
ostream &errorMsg(ostream &os, const Args&... rest)
{
    // print(os, debug_rep(a1), debug_rep(a2), ..., debug_rep(an)
    return print(os, debug_rep(rest)...);
}
The call to print uses the pattern debug_rep(rest). 

16.4.3. Forwarding Parameter Packs

// fun has zero or more parameters each of which is
// an rvalue reference to a template parameter type
template<typename... Args>
void fun(Args&&... args) // expands Args as a list of rvalue references
{
    // the argument to work expands both Args and args
    work(std::forward<Args>(args)...);
}
Here we want to forward all of fun’s arguments to another function named work that presumably does the real work of the function.

16.5. Template Specializations

    It is not always possible to write a single template that is best suited for every possible template argument with which the template might be instantiated. In some cases, the general template definition is simply wrong for a type: The general definition might not compile or might do the wrong thing. At other times, we may be able to take advantage of some specific knowledge to write more efficient code than would be instantiated from the template. When we can’t (or don’t want to) use the template version, we can define a specialized version of the class or function template.
    A specialization is a separate definition of the template in which one or more template parameters are specified to have particular types.






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章