天天看點

16-Templates and Generic Programming

Please indicate the source: http://blog.csdn.net/gaoxiangnumber1

Welcome to my github: https://github.com/gaoxiangnumber1

  • Both object-oriented programming and generic programming deal with types that are not known when the program is written: OOP deals with types that are not known until run time, in GP the types become known during compilation.
  • A template is a formula for creating classes or functions. When we use a generic type, we supply the information needed to transform that formula into a specific class or function. That transformation happens during compilation.

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.
  • A template definition starts with

    template

    followed by a template parameter list, which is a comma-separated list of one or more template parameters bracketed by < and >. The template parameter list cannot be empty.
template<typename T>
int compare(const T &v1, const T &v2)
{
    return ((v1 > v2) ?  : ((v1 == v2) ?  : -));
}
           

Instantiating a Function Template

  • When we call a function template, the compiler uses the arguments of the call to deduce the template arguments for us. The compiler uses the deduced template parameters to instantiate a specific version of the function for us.
// instantiates int compare(const vector<int>&, const vector<int>&)
vector<int> vec1{, , }, vec2{, , };
cout << compare(vec1, vec2) << endl;    // T is vector<int>
           
  • The compiler will generate a version of compare with T replaced by vector. The compiler-generated functions are referred to as an instantiation of the template.

Template Type Parameters

  • 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.
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. Both keywords have the same meaning and can be used interchangeably inside a template parameter list. A template parameter list can use both keywords:
template <typename T, class U>
T calc(const T&, const U&);
           

Nontype Template Parameters

  • Nontype parameters are specified by using a specific type name and they 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) which allows the compiler to instantiate the templates during compile time.
  • We can write compare that handles string literals that are arrays of const char. Because we cannot copy an array, we define our parameters as references to an array(6.2.4). Because we want to compare literals of different lengths, we give our template two nontype parameters: the first represent the size of the first array, the second represent the size of the second array:
template<unsigned N, unsigned M>
int compare(const char (&p1)[N], const char (&p2)[M])
{
    return strcmp(p1, p2);
}
           
  • compare("hi", "mom");

    The compiler use the size of the literals to instantiate a version of the template with the sizes substituted for N and M. Since the compiler inserts a null terminator at the end of a string literal(2.1.3), the compiler will instantiate

    int compare(const char(&p1)[3], const char(&p2)[4]);

  • A nontype parameter may be an integral type, a pointer or (lvalue) reference to an object or to a function type.
    1. Arguments bound to a nontype integral parameter must be a constant expression.
    2. Arguments bound to a pointer or reference nontype parameter must have static lifetime, can’t use an nonstatic local object or a dynamic object. A pointer parameter can be instantiated by nullptr or a zero-valued constant expression.
  • A template nontype parameter is a constant value inside the template definition and it can be used when constant expressions are required, e.g., to specify the size of an array.

inline and constexpr Function Templates

  • The inline or constexpr specifier follows the template parameter list and precedes the return type:
// ok: inline specifier follows the template parameter list
template <typename T>
inline T min(const T&, const T&);
           

Writing Type-Independent Code

  • Two principles for writing generic code:
    1. The function parameters in the template are references to const. Ensure our function can be used on types that cannot be copied(unique_ptr and the IO types). If called with large objects, this design make the function run faster.
    2. The tests in the body use only < comparisons. Reduce the requirements on types that can be used with our function. Those types must support <, but they need not support >.
  • If we were concerned about type independence and portability, we should define function using the less(14.8.2). The problem with original version is that if a user calls it with two pointers and those pointers do not point to the same array, then the result is undefined.
// version of compare that will be correct even if used on pointers; see §14.8.2
template <typename T> int compare(const T &v1, const T &v2)
{
    if (less<T>()(v1, v2)) return -;
    if (less<T>()(v2, v1)) return ;
    return ;
}
           

Template Compilation

  • When the compiler sees the definition of a template, it does not generate code. It generates code only when we instantiate a specific instance of the template.
  • When we call a function, the compiler needs to see only a declaration for the function. When we use objects of class type, the class definition must be available, but the definitions of the member functions need not be present.

    So, we put class definitions and function declarations in header files and definitions of ordinary and class-member functions in source files.

  • For templates, the compiler needs to have the code that defines a function template or class template member function to generate an instantiation. So, headers for templates should include definitions and declarations.

Key Concept: Templates and Headers

  • Templates contain two kinds of names:
    1. Those that do not depend on a template parameter.
    2. Those that do depend on a template parameter.
  • The template provider must ensure:
    1. All names that do not depend on a template parameter are visible when the template is used.
    2. The definition of the template, including the definitions of the members of a class template, are visible when the template is used.
  • The template user must ensure that declarations for all functions, types, and operators associated with the types used to instantiate the template are visible.
    1. Authors of templates should provide a header that contains the template definition along with declarations for all the names used in the class template or in the definitions of its members.
    2. Users of the template must include the header for the template and for any types used to instantiate that template.

Compilation Errors Are Mostly Reported during Instantiation

  • There are three stages during which the compiler might flag an error.
    1. The first stage is when we compile the template itself. The compiler can detect syntax errors but not much else.
    2. The second stage is when the compiler sees a use of the template.

      -1- For a function template, the compiler checks that the number of the arguments is appropriate and whether two arguments that are supposed to have the same type do so.

      -2- For a class template, the compiler checks that the right number of template arguments are provided but not much more.

    3. The third stage when errors are detected is during instantiation. It is only then that type-related errors can be found. Depending on how the compiler manages instantiation, these errors may be reported at link time.
  • The caller should 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.
if (v1 < v2) return -;     // requires < on objects of type T
if (v2 < v1) return ;      // requires < on objects of type T
return ;                   // returns int; not dependent on T
           
  • The argument type has a < operator. When the compiler processes the body of this template, it cannot verify whether the conditions in the if statements are legal. If the arguments passed to compare have a < operation, then the code is fine, but not otherwise. For example,
Sales_data data1, data2;
cout << compare(data1, data2) << endl; // error: no < on Sales_data
           
  • This instantiation generates a version of the function that will not compile. Errors such as this one cannot be detected until the compiler instantiates the definition of compare on type Sales_data.

Exercises Section 16.1.1

Exercise 16.1

Define instantiation.
  • The compiler-generated functions are referred to as an instantiation of the template.

Exercise 16.2

Write and test your own versions of the compare functions.
#include <iostream>

using namespace std;

template<typename T>
int compare(const T &v1, const T &v2)
{
    if(less<T>()(v1, v2))
    {
        return -;
    }
    if(less<T>()(v2, v1))
    {
        return ;
    }
    return ;
}

int main()
{
    cout << compare(, ); // -1

    return ;
}
           

Exercise 16.3

Call your compare function on two Sales_data objects to see how your compiler handles errors during instantiation.
  • error: no match for ‘operator<’ (operand types are ‘const Sales_data’ and ‘const Sales_data’)

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 functions iterator parameters and the other for the type of the value. Use your function to find a given value in a vector and in a list.
#include <iostream>
#include <vector>
#include <list>
#include <string>

using namespace std;

template<typename Iterator, typename Value>
Iterator Find(Iterator first, Iterator last, const Value& value)
{
    for(; first != last && *first != value; ++first);
    return first;
}

int main()
{
    vector<int> v = { , , , , , , , ,  };
    bool is_in_vector = (v.cend() != Find(v.cbegin(), v.cend(), ));
    cout << (is_in_vector ? "found\n" : "not found\n");

    list<string> l = { "aa", "bb", "cc", "dd", "ee", "ff", "gg" };
    bool is_in_list = (l.cend() != Find(l.cbegin(), l.cend(), "zz"));
    cout << (is_in_list ? "found\n" : "not found\n");

    return ;
}
           

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.
#include <iostream>
#include <string>

using namespace std;

template<typename T, unsigned int N>
void Print(const T (&arr)[N])
{
    for(int index = ; index < N; ++index)
    {
        cout << arr[index] << ' ';
    }
    cout << '\n';
}

int main()
{
    int arr1[] = {, , , , , , };
    string arr2[] = { "gao", "xiang", "number", "one" };
    Print(arr1);
    Print(arr2);

    return ;
}
           

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.
#include <iostream>
#include <string>

using namespace std;

template<typename T, unsigned int N>
T* Begin(T (&arr)[N])
{
    return arr;
}

template<typename T, unsigned int N>
T* End(T (&arr)[N])
{
    return arr + N;
}

int main()
{
    int arr1[] = {, , , , , , };
    cout << *Begin(arr1) << ' ' << *(End(arr1) - ) << '\n';
    string arr2[] = { "gao", "xiang", "number", "one" };
    cout << *Begin(arr2) << ' ' << *(End(arr2) - ) << '\n';

    return ;
}
           

Exercise 16.7

Write a constexpr template that returns the size of a given array.
#include <iostream>
#include <string>

using namespace std;

template<typename T, unsigned int N>
constexpr int Size(T (&arr)[N])
{
    return N;
}

int main()
{
    int arr1[] = {, , , , , , };
    string arr2[] = { "gao", "xiang", "number", "one" };
    cout << Size(arr1) << ' ' << Size(arr2) << '\n';

    return ;
}
           

Exercise 16.8

In the Key Concept box on page 108, we noted that as a matter of habit C++ programmers prefer using != to using <. Explain the rationale for this habit.
  • Because more class defines “!=” rather than “<”. Doing so can reduce the requirements of the class used with a template class.

16.1.2. Class Templates

  • Since the compiler cannot deduce the template parameter type(s) for a class template, we must supply the list of template arguments to use in place of the template parameters inside angle brackets following the templates name(3.3).

Defining a Class Template

  • Class templates begin with

    template

    followed by a template parameter list, 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
    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
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 templates parameters. The compiler uses these template arguments to instantiate a specific class from the template.
Blob<int> ia;                   // empty Blob<int>
Blob<int> ia2 = {, , , , };    // Blob<int> with five elements
           
  • When the compiler instantiates a class from Blob template, it rewrites the template by replacing each instance of the template parameter T with the given template argument(int). The compiler will instantiate a class that is equivalent to
template <>
class Blob<int>
{
    typedef typename std::vector<int>::size_type size_type;
    //...
};
           
  • The compiler generates a different class for each element type we specify.
// these definitions instantiate two distinct Blob types
Blob<string> names;     // Blob that holds strings
Blob<double> prices;        // different element type
           

References to a Template Type in the Scope of the Template

  • The name of a class template is not the name of a type(3.3). A class template is used to instantiate a type, and an instantiated type always includes template argument(s).

Member Functions of Class Templates

  • As with any class, we can define the member functions of a class template either inside or outside of the class body; members defined inside the class body are implicitly inline.
  • Each instantiation of the class template has its own version of each member. A member function defined outside the class template body starts with

    template

    followed by the class template parameter list. For member function of Blob:
template <typename T>
return_type Blob<T>::member_name(parameter_list)
           

The check and Element Access Members

template <typename T>
void Blob<T>::check(size_type i, const std::string &msg) const
{
    if(i >= data->size())
        throw std::out_of_range(msg);
}
           
  • The subscript operator and back function use the template parameter to specify the return type.
template <typename T>
T& Blob<T>::back()
{
    check(, "back on empty Blob");
    return data->back();
}
template <typename T>
T& Blob<T>::operator[](size_type i)
{
    // if i is too big, check will throw, preventing access to a nonexistent element
    check(i, "subscript out of range");
    return (*data)[i];
}
template <typename T>
void Blob<T>::pop_back()
{
    check(, "pop_back on empty Blob");
    data->pop_back();
}
           

Blob Constructors

template <typename T>
Blob<T>::Blob(): data(std::make_shared<std::vector<T>>()) {}
template <typename T>
Blob<T>::Blob(std::initializer_list<T> il): data(std::make_shared<std::vector<T>>(il)) {}
           

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. Following codes instantiates the Blob class and three member functions: operator[], size, and the initializer_list constructor.
// instantiates Blob<int> and the initializer_list<int> constructor
Blob<int> squares = {,,,,,,,,,};
// instantiates Blob<int>::size() const
for (size_t i = ; i != squares.size(); ++i)
{
    squares[i] = i*i; // instantiates Blob<int>::operator[](size_t)
}
           
  • The fact that members are instantiated only if we use them lets us instantiate a class with a type that may not meet the requirements for some of the templates operations.

Simplifying Use of a Template Class Name inside Class Code

  • One exception to the rule “Must supply template arguments when using a class template type”: inside the scope of the class template itself, we may use the name of the template without arguments:
// BlobPtr throws an exception on attempts to access a nonexistent element
template <typename T>
class BlobPtr
{
public:
    BlobPtr(): curr() {}
    BlobPtr(Blob<T> &a, size_t sz = ): wptr(a.data), curr(sz) {}
    T& operator*() const
    {
        auto p = check(curr, "dereference past end");
        return (*p)[curr]; // (*p) is the vector to which this object points
    }
    // increment and decrement
    BlobPtr& operator++(); // prefix operators
    BlobPtr& operator--();

private:
    // check returns a shared_ptr to the vector if the check succeeds
    std::shared_ptr<std::vector<T>> check(std::size_t, const std::string&) const;
    // store a weak_ptr, which means the underlying vector might be destroyed
    std::weak_ptr<std::vector<T>> wptr;
    std::size_t curr; // current position within the array
};
           
  • The prefix increment and decrement members of BlobPtr return BlobPtr&, not BlobPtr&. 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 templates own parameters.

Using a Class Template Name outside the Class Template Body

  • When we define members outside the body of a class template, we are not in the scope of the class until the class name is seen(7.4):
// postfix: increment/decrement the object but return the unchanged value
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
}
           
    1. Because the return type appears outside the scope of the class, we must specify that the return type returns a BlobPtr instantiated with the same type as the class.
    2. Inside the function body, we are in the scope of the class, so don’t need to repeat the template argument when we define ret. When we do not supply template arguments, the compiler assumes that we are using the same type as the members instantiation.

Class Templates and Friends

  • If a class contains a friend declaration(7.2.1), the class and the friend can independently be templates or not. When the class is a template:
    1. Friend is a nontemplate: friend access to all the instantiations of the class template.
    2. Friend is 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. For example, Blob class should declare the BlobPtr class and a template version of the Blob equality operator(originally defined for StrBlob in the exercises in 14.3.1(p. 562)) as friends.
  • 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 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)
};
           
  • We start by declaring that Blob, BlobPtr, and operator== are templates. These declarations are needed for the parameter declaration in the operator== function and the friend declarations in Blob.
  • The friend declarations use Blobs 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
           
  • The members of BlobPtr may access the nonpublic parts of ca(or any other Blob object), but ca has no special access to ia(or any other Blob) or to any other instantiation of Blob.

General and Specific Template Friendship

  • A class can 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 Templates Own Type Parameter

  • C++11: 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
    // ...
};
           
  • Whatever type is used to instantiate Bar is a friend of Bar: for type named Foo, Foo would be a friend of Bar.
  • Even though a friend ordinarily must be a class or a function, it is okay for Bar to be instantiated with a built-in type.

Template Type Aliases

  • An instantiation of a class template defines a class type, and we can define a typedef (2.5.1) that refers to that instantiated class:

    typedef Blob<string> StrBlob;

    Because a template is not a type, we cannot define a typedef that refers to a template (e.g., Blob).
  • C++11 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>
twin<int> win_loss; // win_loss is a pair<int, int>
twin<double> area;  // area is a pair<double, double>
           
  • We define twin as a synonym for pairs in which the members have the same type. Users of twin need to specify that type only once.
  • 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>
           
  • We define partNo as a synonym for the family of types that are pairs in which the second member is an unsigned, so users of partNo specify a type for the first member of the pair but have no choice about second.

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: for any given type X, there is one Foo::ctr and one Foo::count member. All objects of type Foo share the same ctr object and count function.
// 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 of each static data member of a template class. But there is a distinct object for each instantiation of a class template. So, we define a static data member as a template.

    template <typename T> size_t Foo<T>::ctr = 0; // define and initialize ctr

  • As with any members of a class template, we start by defining the template parameter list, followed by the type of the member we are defining and the members name.

    As usual, a member’s name includes the member’s class name, which for a class generated from a template includes its template arguments.

    When Foo is instantiated for a particular template argument type, a separate ctr will be instantiated for that class type and initialized to 0.

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

Exercises Section 16.1.2

Exercise 16.9

What is a function template? What is a class template?
  • A function template is a formula from which we can generate type-specific versions of that function.
  • 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. To use a class template we must supply additional information inside angle brackets following the template’s name.

Exercise 16.10

What happens when a class template is instantiated?
  • The compiler uses these template arguments to instantiate a specific class from the template.

Exercise 16.11

The following definition of List is incorrect. How would you fix it?
template <typename elem_Type> class List_Item;
template <typename elem_Type> class List
{
public:
    List<elem_Type>();
    List<elem_Type>(const List<elem_Type> &);
    List<elem_Type>& operator=(const List<elem_Type> &);
    ~List();
    void insert(List_Item *ptr, elem_Type value);

private:
    List_Item *front, *end;
};
           
template <typename elem_Type> class List_Item;
template <typename elem_Type>
class List
{
public:
    List<elem_Type>(){}
    List<elem_Type>(const List<elem_Type> &);
    List<elem_Type>& operator=(const List<elem_Type> &);
    ~List(){}
    // error: ‘List_Item’ is not a type|
    void insert(List_Item<elem_Type> *ptr, elem_Type value); // ok
private:
    // error: invalid use of template-name ‘List_Item’ without an argument list|
    List_Item<elem_Type> *front, *end; // ok
};
           

Exercise 16.12

Write your own version of the Blob and BlobPtr templates. including the various const members that were not shown in the text.
  • Omit.

Exercise 16.13

Explain which kind of friendship you chose for the equality and relational operators for BlobPtr.
  • Omit.

Exercise 16.14

Write a Screen class template that uses nontype parameters to define the height and width of the Screen.
  • Omit.

Exercise 16.15

Implement input and output operators for your Screen template. Which, if any, friends are necessary in class Screen to make the input and output operators work? Explain why each friend declaration, if any, was needed.
  • Omit.

Exercise 16.16

Rewrite the StrVec class(13.5, p. 526) as a template named Vec.
  • Omit.

16.1.3. Template Parameters

  • Like the names of function parameters, a template parameter name has no intrinsic meaning. We ordinarily name type parameters T, but we can use any name.
template <typename Foo>
Foo calc(const Foo& a, const Foo& b)
{
    Foo tmp = a; // tmp has the same type as the parameters and return type
    // ...
    return tmp; // return type and parameters have the same type
}
           

Template Parameters and Scope

  • The name of a template parameter can be used after it has been declared and until the end of the template declaration or definition.

    A template parameter hides any declaration of that name in an outer scope.

    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
}
           
  • Because a parameter name cannot be reused, the name of a template parameter can appear only once with in a given template parameter list.
// error: illegal reuse of template parameter name V
template <typename V, typename V>
// ...
           

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;
           
  • 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; every declaration and the definition of a given template must have the same number and kind(i.e., type or nontype) of parameters.
// 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) { /* . . . */ }
           
  • 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(explain in 16.3).

Using Class Members That Are Types

  • We use the scope operator(::) to access both static members and type members(7.4 and 7.6). For nontemplate, since the compiler knows the class definition, it knows whether a name accessed through :: is a type or a static member.
  • Assuming T is a template type parameter, when the compiler sees

    T::mem

    , it can’t know until instantiation time whether mem is a type or a static data member. But in order to process the template, the compiler must know whether a name represents a type. For example, when the compiler sees

    T::size_type *p;

    , it needs to know whether we’re defining a variable named p or are multiplying a static data member named size_type by a variable named p.
  • By default, the compiler assumes that a name accessed through :: is not a type. If we want to use a type member of a template type parameter, we must tell the compiler that the name is a type 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();
}
           
  • Function top expects a container as its argument and uses typename to specify its return type and to generate a value initialized element(7.5.3) to return if c has no elements.

Default Template Arguments

  • C++11: we can supply default arguments for both function and class templates.
// compare has a default template argument, less<T> and a default function argument, F()
template <typename T, typename F = less<T>>
int compare(const T &v1, const T &v2, F f = F())
{
    if (f(v1, v2)) return -;
    if (f(v2, v1)) return ;
    return ;
}
           
  • The type parameter, F, represents the type of a callable object(10.3.2) and the function parameter, f, will be bound to a callable object.

    The default template argument specifies that compare will use the library less function-object class, instantiated with the same type parameter as compare. The default function argument says that f will be a default-initialized object of type F.

bool i = compare(, ); // uses less<int>; i is -1
// result depends on the isbn in item1 and item2
Sales_data item1(cin), item2(cin);
bool j = compare(item1, item2, compareIsbn);
           
  • As with function default arguments, a template parameter may have a default argument only if all of the parameters to its right also have default arguments.

Template Default Arguments and Class Templates

  • Whenever we use a class template, we must follow the templates name with brackets. The brackets indicate that a class must be instantiated from a template. 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 templates name.
template <class T = int>
class Numbers // by default T is int
{
public:
    Numbers(T v = ): 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
           

Exercises Section 16.1.3

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?
  • No differences.
  • When we indicate a template class’s type, not static data member, we must use typename.

Exercise 16.18

Explain each of the following function template declarations and identify whether any are illegal. Correct each error that you find.
(a) template <typename T, U, typename V> void f1(T, U, V);
(b) template <typename T> T f2(int &T);
(c) inline template <typename T> T foo(T, unsigned int*);
(d) template <typename T> f4(T, T);
(e) typedef char Type;
template <typename Type> Type f5(Type a);
           
(a) template <typename T, typename U, typename V> // add typename for U
void f1(T, U, V);
(b) template <typename T> T f2(int &); // Delete T in parameter list
(c) template <typename T> // Move “inline” just before return type
inline T foo(T, unsigned int*);
(d) template <typename T> void f4(T, T); // Add return type
(e) Right.
           

Exercise 16.19

Write a function that takes a reference to a container and prints the elements in that container. Use the containers size_type and size members to control the loop that prints the elements.
  • See 16.20

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 <string>

using namespace std;

template<typename Container>
void PrintUseSize(Container &obj)
{
    typename Container::size_type length = obj.size(), index = ;
    typename Container::iterator it = obj.begin();
    for(; index < length; ++index)
    {
        cout << *it << ' ';
        ++it;
    }
    cout << '\n';
}

template<typename Container>
void PrintUseIter(Container &obj)
{
    typename Container::iterator it = obj.begin(), last = obj.end();
    for(; it != last; ++it)
    {
        cout << *it << ' ';
    }
    cout << '\n';
}

int main()
{
    vector<string> vec_string = { "gao", "xiang", "number", "one" };
    vector<int> vec_int = { , , ,  };
    PrintUseSize(vec_string);
    PrintUseSize(vec_int);

    PrintUseIter(vec_string);
    PrintUseIter(vec_int);

    return ;
}
/*
Output:
gao xiang number one 
71 88 80 7188 
gao xiang number one 
71 88 80 7188 
*/
           

16.1.4. Member Templates

  • A class(either nontemplate or template) may have a member function that is itself a template. Such members are called member templates that may not be virtual.

Member Templates of Ordinary(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;
DebugDelete()(ip);  // calls operator()(int*) on a temporary DebugDelete object

// destroying the the object to which p points
// instantiate 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());
           
  • Whenever unique_ptrs destructor is instantiated, DebugDelete call operator will also be instantiated. 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

  • When we define a member template of a class template, both the class and the member have their own, independent, template parameters.
template <typename T>
class Blob
{
    template <typename It> Blob(It b, It e);
    // ...
};
           
  • When we define a member template outside the body of a class template, we must provide the parameter list for the class template first, followed by the members own template parameter list.
template <typename T> // type parameter for the class
template <typename It> // type parameter for the constructor
Blob<T>::Blob(It b, It e): data(std::make_shared<std::vector<T>>(b, e)) {}
           

Instantiation and Member Templates

  • To instantiate a member template of a class template, we must supply arguments for the template parameters for both the class and the function templates.
    1. Argument(s) for the class template parameter(s) are determined by the type of the object through which we call the member template.
    2. The compiler deduces template argument(s) for the member templates own parameter(s) from the arguments passed in the call(16.1.1).
int ia[] = {,,,,,,,,,};
vector<long> vi = {,,,,,,,,,};
list<const char*> w = {"now", "is", "the", "time"};

// instantiates Blob<int> class; Blob<int> constructor has two int* parameters
Blob<int> a1(begin(ia), end(ia));

// instantiates Blob<int> constructor that has two vector<long>::iterator parameters
Blob<int> a2(vi.begin(), vi.end());

// instantiates Blob<string> class and
// Blob<string> constructor has two list<const char*>::iterator parameters
Blob<string> a3(w.begin(), w.end());
           
  • When defining a1, the compiler instantiate a version of Blob with the template parameter bound to int. The definition of a1 instantiates:

    Blob<int>::Blob(int*, int*);

  • The definition of a2 uses the already instantiated Blob class and instantiates the constructor with It replaced by vector::iterator.

Exercises Section 16.1.4

Exercise 16.21

Write your own version of DebugDelete.
  • Same as above.

Exercise 16.22

Revise your TextQuery programs from 12.3(p. 484) so that the shared_ptr members use a DebugDelete as their deleter(12.1.4, p. 468).
  • Omit.

Exercise 16.23

Predict when the call operator will be executed in your main query program. If your expectations and what happens differ, be sure you understand why.
  • Omit.

Exercise 16.24

Add a constructor that takes two iterators to your Blob template.
  • Omit.

16.1.5. Controlling Instantiations

  • Since instantiations are generated when a template is used(16.1.1), the same instantiation may appear in multiple object files. When several 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.
  • Under C++11, we can avoid the overhead of instantiating the same template in multiple files by an explicit instantiation that has the form
extern template declaration;    // instantiation declaration
template declaration;           // instantiation definition
           

where declaration is a class or function declaration in which all the template parameters are replaced by the template arguments.

extern template class Blob<string>;         // instantiation declaration
template int compare(const int&, const int&);   // instantiation definition
           
  • When the compiler sees an extern template declaration, it will not generate code for that instantiation in that file. There may be several extern declarations for a given instantiation but there must be exactly one definition for that instantiation.
  • Because the compiler automatically instantiates a template when we use it, the extern declaration must appear before any code that uses 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 = {,,,,,,,,,,};        
Blob<int> a2(a1);           // copy constructor instantiated in this file
int i = compare(a1[], a2[]);  // instantiation will appear elsewhere
           
  • Application.o contains instantiations for Blob, initializer_list and copy constructors for that class.

    The compare function and Blob class will not be instantiated in that file. There must be definitions of these templates in some other file in the program.

// Build.cc
// instantiation file must provide a definition for
// every type and function that other files declare as extern
template int compare(const int&, const int&);
template class Blob<string>; // instantiates all members of the class template
           
  • When the compiler sees an instantiation definition, it generates code. Build.o contains the definitions for compare instantiated with int and for Blob class. When we build the application, we must link Build.o with Application.o.

Instantiation Definitions Instantiate All Members

  • Because the compiler cannot know which member function the program uses when it sees an instantiation definition, the compiler instantiates all the members of that class. So, we can use explicit instantiation only for types that can be used with all the members of that template.

Exercises Section 16.1.5

Exercise 16.25

Explain the meaning of these declarations:
extern template class vector<string>;
template class vector<Sales_data>;
           
  • Instantiation declaration: the vector class will not be instantiated in that file. There must be definitions of these templates in some other file in the program.
  • Instantiation definition: the vector class will be instantiated in that file.

Exercise 16.26

Assuming NoDefault is a class that does not have a default constructor, can we explicitly instantiate vector? If not, why not?
  • No, because “we can use explicit instantiation only for types that can be used with all the members of that template.” vector has constructors that need its parameter’s default constructor.
#include <vector>

using namespace std;

class NoDefault
{
public:
    NoDefault(int){}
};

// error: no matching function for call to ‘NoDefault::NoDefault()’|
template class vector<NoDefault>;

int main()
{
    return ;
}
           

Exercise 16.27

For each labeled statement explain what, if any, instantiations happen. If a template is instantiated, explain why; if not, explain why not.
template <typename T> class Stack {};
void f1(Stack<char>);               // (a) Only declaration, not instantiate.
class Exercise
{
    Stack<double> &rsd;             // (b) Don’t instantiate for class definition.
    Stack<int> si;                  // (c) Instantiate only when creating class’s object.
};
int main()
{
    Stack<char> *sc;                    // (d) Not create object.
    f1(*sc);                            // (e) Use copy constructor to instantiate object.
    int iObj = sizeof(Stack< string >); // (f) Not create object, only use class definition.
}
           
#include <iostream>
#include <vector>
#include <string>

using namespace std;

template <typename T>
class Stack
{
public:
    Stack()
    {
        cout << "Stack()\n";
    }
    Stack(const Stack &)
    {
        cout << "Stack(const Stack&)\n";
    }

};

void f1(Stack<char>); // (a)

class Exercise
{
    Stack<double> &rsd; // (b)
    Stack<int> si; // (c)
};

int main()
{
    Stack<char> *sc; // (d)
    f1(*sc); // (e)
    int iObj = sizeof(Stack< string >); // (f)
}

void f1(Stack<char> obj) {}
           

16.1.6. Efficiency and Flexibility

  • The difference between shared_ptr and unique_ptr is the strategy they use in managing the pointer they hold: one gives us shared ownership, the other owns the pointer that it holds.
  • They also differ in how they let users override their default deleter:
    1. We can override the deleter of a shared_ptr by passing a callable object when we create or reset the pointer.
    2. Since the type of the deleter is part of the type of a unique_ptr object, we must supply that type as an explicit template argument when defining a unique_ptr.

      This difference in implementation strategy have important performance impacts.

Binding the Deleter at Run Time

  • shared_ptr doesn’t hold the deleter as a direct member because the type of the deleter isn’t known until run time. Since we can’t have a member whose type changes at run time, the deleter must be stored indirectly(a pointer or a class(such as function 14.8.3) that encapsulates a pointer).
  • Assume shared_ptr stores the pointer it manages in member p, and the deleter is accessed through member del. The destructor must include such statement:
// value of del known only at run time; call through a pointer
del ? del(p) : delete p; // del(p) requires run-time jump to del's location
           
  • Because the deleter is stored indirectly, the call del(p) requires a run-time jump to the location stored in del to execute the code to which del points.

Binding the Deleter at Compile Time

  • unique_ptr has two template parameters: one represents the pointer that it manages and the other represents the type of the deleter. Because the deleter type is part of the unique_ptr type, the type of the deleter member is known at compile time, so the deleter can be stored directly in each unique_ptr object.
  • The unique_ptr destructor calls a user-supplied deleter or executes delete(the default deleter type) on its stored pointer.
// del bound at compile time; direct call to the deleter is instantiated
del(p); // no run-time overhead
           
  • 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.

Exercises Section 16.1.6

Exercise 16.28

Write your own versions of shared_ptr and unique_ptr.
  • Omit.

Exercise 16.29

Revise your Blob class to use your version of shared_ptr rather than the library version.
  • Omit.

Exercise 16.30

Rerun some of your programs to verify your shared_ptr and revised Blob classes.(Note: Implementing the weak_ptr type is beyond the scope of this Primer, so you will not be able to use the BlobPtr class with your revised Blob.)
  • Omit.

Exercise 16.31

Explain how the compiler might inline the call to the deleter if we used DebugDelete with unique_ptr.
  • Omit.

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

  • The arguments passed in a call to a function template initialize that functions parameters. All conversions automatically applied to function parameters whose type is a template type parameter:
    1. Top-level consts(2.4.3) in parameter/argument are ignored.
    2. Const conversions: A function parameter that is a reference/pointer to a const can be passed a reference/pointer to a nonconst object(4.11.2).
    3. Array- or function-to-pointer conversions: If the function parameter is not a reference, then the pointer conversion will be applied to arguments of array or function type.

      -1- An array argument will be converted to a pointer to its first element.

      -2- A function argument will be converted to a pointer to the functions type(4.11.2).

  • Consider calls to functions f_obj and f_ref. The f_obj function copies its parameters, whereas f_refs parameters are references:
template <typename T>
T f_obj(T, T);              // arguments are copied
template <typename T>
T f_ref(const T&, const T&);    // references

string s1("a value");
const string s2("another value");
f_obj(s1, s2);  // calls f_obj(string, string); top-level const is ignored
f_ref(s1, s2);      // calls f_ref(const string&, const string&)
                // uses permissible conversion to const on s1
int a[], b[];   // note: size is part of array type
f_obj(a, b);        // calls f(int*, int*)
f_ref(a, b)     // error: array types don't match
           
  • The last call: when the parameter is a reference, array is not converted to pointer (6.2.4). The types of a and b don’t match, so the call is in error.

Function Parameters That Use the Same Template Parameter Type

  • Because there are limited conversions, the arguments to parameters that use the same template parameter type must have essentially the same type. If the deduced types do not match, the call is an error.
// compare(16.1.1) takes two const T& parameters
long num;
compare(num, ); // error: cannot instantiate compare(long, int)
           
  • If we want to allow normal conversions on the arguments, we can define the function with two type parameters.
// argument types can differ but must be compatible
template <typename A, typename B>
int Compare(const A& v1, const B& v2)
{
    if (v1 < v2) return -;
    if (v2 < v1) return ;
    return ;
}

long num;
Compare(num, ); // ok: calls Compare(long, int)
           

Normal Conversions Apply for Ordinary Arguments

  • A function template can have parameters that are defined using ordinary types that do not involve a template type parameter. Such arguments are converted as usual to the corresponding type of the parameter(6.1).
template <typename T>
ostream &print(ostream &os, const T &obj)
{
    return os << obj;
}

print(cout, );    // instantiates print(ostream&, int)
ofstream f("output");
print(f, );       // uses print(ostream&, int); converts f to ostream&
           
  • In the second call, the first argument is an ofstream and there is a conversion from ofstream to ostream&(8.2.1). Because the type of this parameter does not depend on a template parameter, the compiler will implicitly convert f to ostream&.

Exercises Section 16.2.1

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.

Exercise 16.33

Name two type conversions allowed on function arguments involved in template argument deduction.
    1. Const conversions: A function parameter that is a reference/pointer to a const can be passed a reference/pointer to a nonconst object(4.11.2).
    2. Array- or function-to-pointer conversions: If the function parameter is not a reference, then the pointer conversion will be applied to arguments of array or function type.

      -1- An array argument will be converted to a pointer to its first element.

      -2- A function argument will be converted to a pointer to the functions type(4.11.2).

Exercise 16.34

Given only the following code, explain whether each of these calls is legal. If so, what is the type of T? If not, why not?
template <class T> int compare(const T&, const T&);
(a) compare("hi", "world");
(b) compare("bye", "dad");
           
//error: no matching function for call to ‘compare(const char [3], const char [6])’
compare("hi", "world");
// ok, same as `int compare(const char (&)[3], const char (&)[3]);`
Compare("bye", "dad");
           

Exercise 16.35

Which, if any, of the following calls are errors? If the call is legal, what is the type of T? If the call is not legal, what is the problem?
template <typename T> T calc(T, int);
template <typename T> T fcn(T, T);
double d;
float f;
char c;
(a) calc(c, 'c');
(b) calc(d, f);
(c) fcn(c, 'c');
(d) fcn(d, f);
           
(a) calc(c, 'c');   // legal. T is char.
(b) calc(d, f); // legal. T is double.
(c) fcn(c, 'c');    // legal. T is char.
(d) fcn(d, f);      // illegal. double is not the same type as float.
           

Exercise 16.36

What happens in the following calls:
template <typename T> f1(T, T);
template <typename T1, typename T2> f2(T1, T2);
int i = , j = , *p1 = &i, *p2 = &j;
const int *cp1 = &i, *cp2 = &j;
(a) f1(p1, p2);
(b) f2(p1, p2);
(c) f1(cp1, cp2);
(d) f2(cp1, cp2);
(e) f1(p1, cp1);
(f) f2(p1, cp1);
           
(a) f1(p1, p2); // f1(int*, int*)
(b) f2(p1, p2); // f2(int*, int*)
(c) f1(cp1, cp2);   // f1(const int*, const int*)
(d) f2(cp1, cp2);   // f2(const int*, const int*)
(e) f1(p1, cp1);    // illegal.
(f) f2(p1, cp1);    // f2(int*, const int*)
           

16.2.2. Function-Template Explicit Arguments

Specifying an Explicit Template Argument

  • Define function template sum that takes arguments of two different types. Let user control the type of the return by defining a third template parameter to represent the return type.
// T1 cannot be deduced: it doesn't appear in the function parameter list
template <typename T1, typename T2, typename T3>
T1 sum(T2, T3);
           
  • 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. We supply explicit template argument(s) to a call inside angle brackets after the function name and before the argument list.
// T1 is explicitly specified; T2 and T3 are inferred from the argument types
auto val3 = sum<long long>(i, long_num); // long long sum(int, long)
           
  • Explicit template argument(s) are matched to corresponding template parameter(s) from left to right: the first template argument is matched to the first template parameter, the second argument to the second parameter, and so on.

    An explicit template argument may be omitted only for the right-most parameters, and then only if these can be deduced from the function parameters.

// poor design: users must explicitly specify all three template parameters
template <typename T1, typename T2, typename T3>
T3 alternative_sum(T2, T1);
// have to specify arguments for all three parameters.
// error: can't infer initial template parameters
auto val3 = alternative_sum<long long>(i, long_num);
// ok: all three parameters are explicitly specified
auto val2 = alternative_sum<long long, int, long>(i, long_num);
           

Normal Conversions Apply for Explicitly Specified Arguments

  • As parameters defined by ordinary types(16.2.1), normal conversions also apply for arguments whose template type parameter is explicitly specified.
long long_num;
compare(long_num, );            // error: template parameters don't match
compare<long>(long_num, );      // ok: instantiates compare(long, long)
compare<int>(long_num, );       // ok: instantiates compare(int, int)
           
  • The first call is error because the arguments to compare must have the same type.
  • If we explicitly specify the template parameter type, normal conversions apply. The call

    compare<long>

    is equivalent to calling a function taking two const long&. The int parameter is automatically converted to long.

    In the second call, T is explicitly specified as int, so long_num is converted to int.

Exercises Section 16.2.2

Exercise 16.37

The library max function has two function parameters and returns the larger of its arguments. This function has one template type parameter. Could you call max passing it an int and a double? If so, how? If not, why not?
int a = ;
double b = ;
//error: no matching function for call to ‘max(int&, double&)’
cout << max(a, b);
cout << max<int>(a, b); // ok.
           

Exercise 16.38

When we call make_shared(12.1.1), we have to provide an explicit template argument. Explain why that argument is needed and how it is used.
  • Why: Without specified type, make_shared can’t determine how big the size it should allocate.
  • How: Depending on the type specified, make_shared allocates proper size of memory space and returns a proper type of shared_ptr pointing to it.

Exercise 16.39

Use an explicit template argument to make it sensible to pass two string literals to the original version of compare from 16.1.1(p. 652).
#include <iostream>
#include <string>

using namespace std;
template <typename T>
int compare(const T &v1, const T &v2)
{
    if (v1 < v2) return -;
    if (v2 < v1) return ;
    return ;
}

int main()
{
    compare<string>("gao", "xiang");

    return ;
}
           

16.2.3. Trailing Return Types and Type Transformation

  • If we want to define a function that takes a pair of iterators denoting a sequence and returns a reference to an element in the sequence.
template <typename It>
??? &fcn(It beg, It end)
{
    // process the range
    return *beg; // return a reference to an element from the range
}
vector<int> vi = {,,,,};
Blob<string> ca = { "hi", "bye" };
auto &i = fcn(vi.begin(), vi.end());        // fcn should return int&
auto &s = fcn(ca.begin(), ca.end());    // fcn should return string&
           
  • We can use

    decltype(*beg)

    to obtain the type of this expression, but beg doesn’t exist until the parameter list has been seen.

    We must use a trailing return type(6.3.3) because a trailing return appears after the parameter list, so it can use the functions parameters.

// 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
}
           
  • The dereference operator(*) returns an lvalue(4.1.1), so the type deduced by decltype is a reference to the type of the element that beg denotes.

The Type Transformation Library Template Classes

  • To obtain the element type, we can use a library type transformation template(defined in ). These templates are described in Table 16.1; how they are implemented in 16.5.
16-Templates and Generic Programming
  • Each type transformation template has a public member

    type

    that represents a type. If we instantiate remove_reference with a reference type, then

    type

    is the referred-to type. Given beg is an iterator,

    remove_reference<decltype(*beg)>::type

    is the type of the element to which beg refers.
  • Using remove_reference and a trailing return with decltype, we can write a function to return a copy of an elements value.
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
}
           
  • type

    is a member of class that depends on a template parameter, and we must use

    typename

    to tell the compiler that type represents a type(16.1.3).

Exercises Section 16.2.3

Exercise 16.40

Is the following function legal? If not, why not? If it is legal, what, if any, are the restrictions on the argument type(s) that can be passed, and what is the return type?
template <typename It>
auto fcn3(It beg, It end) -> decltype(*beg + )
{
    // 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.
// Only type that support `this + 0` can be passed. The return type depends on
// the type that operator + returns. In the case below, the return type is Bar.

#include <iostream>
#include <vector>
#include <string>

class Bar {};

Bar operator +(Bar lhs, int)
{
    return lhs;
}

template <typename It>
auto fcn3(It beg, It end) -> decltype(*beg + )
{
    return *beg;  // return a copy of an element from the range
}

int main()
{
    std::vector<Bar> v;
    v.push_back(Bar());
    Bar b = fcn3(v.begin(), v.end());
}
           

16.2.4. Function Pointers and Argument Deduction

  • When we initialize or assign a function pointer(6.7) from a function template, the compiler uses the type of the pointer to deduce the template argument(s).
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;
           
  • The type of the parameters in pointer determines the type of the template argument. The pointer pf1 points to the instantiation of compare with T bound to int. It is an error if the template arguments cannot be determined from the function pointer type.
// overloaded versions of func; each takes a different function pointer type
void func(int(*)(const string&, const string&));
void func(int(*)(const int&, const int&));
func(compare); // error: which instantiation of compare?
           
  • Problem: By looking at the type of func’s parameter, we can’t determine a unique type for the template argument. The call to func could instantiate the version of compare that takes ints or the version that takes strings. Because we can’t identify a unique instantiation for the argument to func, this call won’t compile.

    We can disambiguate the call to func by using explicit template arguments:

// ok: explicitly specify which version of compare to instantiate
func(compare<int>); // passing compare(const int&, const int&)
           
  • When the address of a function-template instantiation is taken, the context must allow a unique type or value to be determined for each template parameter.

16.2.5. Template Argument Deduction and References

  • For deduction from a call to a function such as

    template <typename T> void f(T &p);

    in which the parameter p is a reference to a template type parameter T, remember 2 points:
    1. Normal reference binding rules apply.
    2. consts are low level, not top level.

Type Deduction from Lvalue Reference Function Parameters

  • When a function parameter is an lvalue reference to a template type parameter(T&), we can pass only an lvalue. That argument might or might not have a const type; if it is const, 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; T is int
f1(ci); // ci is a const int; T is const int
f1();  // error: argument to a & parameter must be an lvalue
           
  • When the function parameter has type

    const T&

    , we can pass any kind of argument: an object(const or nonconst), a temporary, or a literal value.

    When the function parameter is const itself, the type deduced for T is not a const type because the const is part of the function parameter type, it doesn’t also become part of the template parameter type.

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 three calls, f2's function parameter is inferred as const int&
f2(i);  // i is an int; T is int
f2(ci); // ci is a const int; T is int
f2();  // 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: T&&), we can pass an rvalue to this parameter and the deduced type for T is the type of the rvalue.
template <typename T> void f3(T&&);
f3(); // argument is an rvalue of type int; template parameter T is int
           

Reference Collapsing and Rvalue Reference Parameters

  • We can’t bind an rvalue reference(T&&) to an lvalue (T&) except two conditions.
    1. When we pass an lvalue(T&) to a function parameter that is an rvalue reference to a template type parameter(T&&), the compiler deduces the template type parameter as the arguments lvalue reference type(T&).

      f3(i); // i is an int object; the deduced type of T is int&, not int.

      T is

      int&

      means f3’s function parameter is an rvalue reference to

      int&

      . We can’t directly define a reference to a reference(2.3.1), but we can do so indirectly through a type alias(2.5.1) or a template type parameter.
    2. If we indirectly create a reference to a reference, then those references collapse.

      (1) In all but one case, the references collapse to form an lvalue reference type.

      (2) 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&.

      X&& &&

      collapses to X&&.
  • Reference collapsing applies only when a reference to a reference is created indirectly(type alias or template parameter).
  • When we pass an lvalue to f3’s rvalue reference function parameter, the compiler will deduce T as an lvalue reference type.
f3(i);  // argument is an lvalue; template parameter T is int&
f3(ci); // argument is an lvalue; template parameter T is const int&
           
  • When a template parameter T is deduced as a reference type, the collapsing rule says that the function parameter T&& collapses to an lvalue reference type. The resulting instantiation for f3(i) would be:
// 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&. So, this call instantiates f3 with an lvalue reference type(int&).
  • Two consequences from these rules:
    1. A function parameter that is an rvalue reference to a template type parameter(e.g., T&&) can be bound to an lvalue.
    2. 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 lvalue reference parameter(T&).
  • Summary: We can pass any type of argument to a function parameter that is an rvalue reference to a template parameter type(T&&). When an lvalue is passed, the function parameter is instantiated as an 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
    {
        /* ... */
    }
}
           
    1. Call f3 on an rvalue(e.g., 42): T is int. The local variable t is an int and is initialized by copying the value of val. When we assign to t, val remains unchanged.
    2. Call f3 on an lvalue(e.g., int i): T is int&. t is int& and the initialization of t binds t to val. When we assign to t, we also change val. The if test will always be true.
  • Rvalue reference parameters are used in two contexts:
    1. The template is forwarding its arguments(16.2.7).
    2. The template is overloaded(16.3).
  • Function templates that use rvalue references often use overloading as in 13.6.3:
template <typename T> void f(T&&);      // binds to modifiable(nonconst) rvalues
template <typename T> void f(const T&); // lvalues and const rvalues
           

Exercises Section 16.2.5

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 = ;
const int ci = i;

(a) g(i);
(b) g(ci);
(c) g(i * ci);
           
  • g(i) T: int& val: int&
  • g(ci) T: const int& val: const int&
  • g(i * ci) T: int val: int&&

Exercise 16.43

Using the function defined in the previous exercise, what would the template parameter of g be if we called g(i = ci)?
  • (i = ci) returns lvalue reference to i. T is int&, val is 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&&). What if gs function parameter is const T&?
  • int/int/int
  • int/int/int&&

16.2.6. Understanding std::move

  • The library move function(13.6.1) is a function template that uses rvalue references. Though we cannot directly bind an rvalue reference to an lvalue, we can use move to obtain an rvalue reference bound to an lvalue(13.6.2).

How std::move Is Defined

  • The standard defines move as follows:
// remove_reference in §16.2.3
template <typename T>
typename remove_reference<T>::type&& move(T&& t)
{
    // static_cast in §4.11.3
    return static_cast<typename remove_reference<T>::type&&>(t);
}
           
  • move’s function parameter, T&&, is an rvalue reference to a template parameter type. Through reference collapsing, this parameter can match arguments of any type(either lvalue or rvalue).
string s1("hi!"), s2;
s2 = std::move(string("hi"));   // ok: moving from an rvalue
s2 = std::move(s1);         // ok: but s1 has indeterminate value after assignment
           

How std::move Works

  • First Assignment.

    move’s argument is the rvalue result of the string constructor. When we pass an rvalue to an rvalue reference function parameter, the deduced type is the referred-to type(16.2.5). So: T is string;

    remove_reference<T>::type

    is string; return type is string&&; t is string&&.

    This call instantiates

    move<string> string&& move(string &&t)

    . The body returns

    static_cast<string&&>(t)

    , since t is string&&, so the cast does nothing.
  • Second Assignment. move’s argument is an lvalue, so: T is string&; remove_reference

Exercises Section 16.2.6

Exercise 16.46

Explain this loop from StrVec::reallocate in 13.5(p. 530):
for(size_t i = ; i != size(); ++i)
    alloc.construct(dest++, std::move(*elem++));
           
    • returns a lvalue, std::move change it to rvalue. The function construct takes only rvalue reference.

16.2.7. Forwarding

  • Some functions need to forward one or more of their arguments with their types unchanged to another, forwarded-to, function; we need preserve everything about the forwarded arguments.
// template that takes a callable and two parameters
// and calls the given callable with the two arguments in reverse order
// flip1 is an incomplete implementation: top-level const and references are lost
template <typename F, typename T1, typename T2>
void flip1(F f, T1 t1, T2 t2)
{
    f(t2, t1);
}
           
  • This template works fine until we want to use it to call a function that has a reference parameter.
void f(int v1, int &v2)
{
    cout << v1 << " " << ++v2 << endl;
}
           
  • f changes the value of the argument bound to v2. if we call f through flip1, the changes made by f do not affect the original argument:
f(, i);           // f changes argument i
flip1(f, j, );    // f called through flip1 leaves j unchanged
           
  • Problem is that the instantiation of this call to flip1 is

    void flip1(void(*fcn)(int, int&), int t1, int t2);

    The value of j is copied into t1; the reference parameter in f is bound to t1, not to j.

Defining Function Parameters That Retain Type Information

  • A function parameter that is an rvalue reference to a template type parameter(T&&) preserves the constness and lvalue/rvalue property of its corresponding argument.
    1. Using a reference parameter(lvalue or rvalue) preserve constness because the const in a reference type is low-level.
    2. Through reference collapsing(16.2.5), if we define the function parameters as T1&& and T2&&, we can preserve the lvalue/rvalue property of arguments.
template <typename F, typename T1, typename T2>
void flip2(F f, T1 &&t1, T2 &&t2)
{
    f(t2, t1);
}
           
  • For the call

    flip2(f, j, 42);

    1. Lvalue j is passed to the parameter t1; T1 is int& and the type of t1 collapses to int&.
    2. The reference t1 is bound to j.
    3. When body calls f, the reference parameter v2 in f is bound to t1, which in turn is bound to j. When f increments v2, it is changing the value of j.
  • Now, flip2 works fine for functions that take lvalue references but cannot be used to call a function that has an rvalue reference parameter.
void g(int &&i, int &j)
{
    cout << i << " " << j << endl;
}
           
  • When we call g through flip2, we pass t2 to g’ s rvalue reference parameter i. Even if we pass an rvalue to flip2:

    flip2(g, i, 42); // error: can't initialize int&& from an lvalue

    Since a function parameter is an lvalue expression(13.6.1), the call to g in flip2 passes an lvalue t2 to g’ s rvalue reference parameter.

Using std::forward(C++11) to Preserve Type Information in a Call

  • std::forward(defined in ) preserves the types of the original arguments. forward must be called with an explicit template argument(16.2.2) and it returns an rvalue reference to this explicit argument type, i.e., forward return T&&.
  • We use forward to pass a function parameter that is defined as an rvalue reference to a template type parameter. Through reference collapsing on its return type, forward preserves the lvalue/rvalue nature of its given argument.
template <typename Type>
intermediary(Type &&arg)
{
    Fun(std::forward<Type>(arg)); // ...
}
           
  • Because arg is an rvalue reference to a template type parameter, Type represents all the type information in the argument passed to arg.
    1. If argument is an rvalue, Type is a nonreference type and forward returns Type&&.
    2. If argument is an lvalue, Type is an lvalue reference type through reference collapsing. The return type is an rvalue reference to an lvalue reference type. forward return an lvalue reference type through reference collapsing.
  • When used with a function parameter that is an rvalue reference to template type parameter(T&&), forward preserves all the details about an arguments 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));
}
           
  • We shouldn’t provide using declaration for std::forward and std::move(18.2.3).

Exercises Section 16.2.7

Exercise 16.47

Write your own version of the flip function and test it by calling functions that have lvalue and rvalue reference parameters.
#include <iostream>
#include <memory>

void func_lvalue(std::string& lhs, std::string& rhs)
{
    lhs = "Gao\n";
    rhs = "Xiang\n";
}

void func_rvalue(int&& lhs, int&& rhs)
{
    // allocate enough space
    std::allocator<int> alloc;
    int* data(alloc.allocate());

    // move into the spaced newly allocated
    alloc.construct(data    , lhs);
    alloc.construct(data + , );
    alloc.construct(data + , rhs);

    // print
    for (auto p = data; p != data + ;  ++p)
        std::cout << *p << "\n";

    // destroy and deallocation
    for (auto p = data +; p != data;    )
        alloc.destroy(--p);
    alloc.deallocate(data, );
}

template<typename F, typename T1, typename T2>
void flip(F f, T1&& t1, T2&& t2)
{
    f(std::forward<T2>(t2), std::forward<T1>(t1));
}

int main()
{
    // test for lvalue reference
    std::string s1, s2;
    flip(func_lvalue, s1, s2);
    std::cout << s1 << s2;

    // test for rvalue reference
    flip(func_rvalue, , );
}
           

16.3. Overloading and Templates

  • Functions with the same name must differ either as to the number or the types of their parameters. Function templates can be overloaded by other templates or by nontemplate functions.
  • Function matching(6.4) 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) 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. The conversions used to call a function template are limited(16.2.1).
  • As usual, if exactly one function provides a better match than any of the others, that function is selected. If there are several functions that provide an equally good match, then:
    1. If there is only one nontemplate function in the set of equally good matches, the nontemplate function is called.
    2. 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.
    3. Otherwise, the call is ambiguous.

Writing Overloaded Templates

// print any type we don't otherwise handle
template <typename T>
string debug_rep(const T &t)
{
    ostringstream ret;  // 8.3
    ret << t;               // uses T's output operator to print a representation of t
    return ret.str();       // return a copy of the string to which ret is bound
}
           
  • This function can be used to generate a string corresponding to an object of any type that has an output operator.
// print pointers as their pointer value, followed by the object to which the pointer points
// NB: this function will not work properly with char*; see 16.3
template <typename T>
string debug_rep(T *p)
{
    ostringstream ret;
    ret << "pointer: " << p;            // print the pointer's own value
    if (p)
        ret << " " << debug_rep(*p);    // print the value to which p points
    else
        ret << " null pointer";         // or indicate that the p is null
    return ret.str();                   // return a copy of the string to which ret is bound
}
           
  • This version generates a string that contains the pointers own value and calls debug_rep to print the object to which that pointer points. This function can’t be used to print character pointers, because the IO library defines a version of << for char* values that assumes the pointer denotes a null-terminated character array, and prints the contents of the array, not its address.
string s("hi");
cout << debug_rep(s) << endl;   #only the first version is viable.
cout << debug_rep(&s) << endl;  #both functions generate viable instantiations
           
  • For the second call:
    1. debug_rep(const string* &)

      is the first instantiation with T bound to string*.
    2. debug_rep(string*)

      is the second instantiation with T bound to string.

      Since the first instantiation requires a conversion from a plain pointer to a pointer to const, the second instantiation is an exact match, so the second template is run.

Multiple Viable Templates

const string *sp = &s;
cout << debug_rep(sp) << endl;
           
  • Both templates are viable and both provide an exact match:

    debug_rep(const string* &)

    the first instantiation with T bound to const string*;

    debug_rep(const string*)

    the second instantiation with T bound to const string.

    debug_rep(const T&)

    is more general than

    debug_rep(T*)

    because it can be called on any type, whereas

    debug_rep(T*)

    can be called only on pointer types. So, this call resolves to

    debug_rep(T*)

Nontemplate and Template Overloads

// print strings inside double quotes
string debug_rep(const string &s)
{
    return '"' + s + '"';
}

string s("hi");
cout << debug_rep(s) << endl;
           
  • Two equal good viable functions:

    debug_rep<string>(const string&)

    the first template with T bound to string;

    debug_rep(const string&)

    the ordinary, nontemplate function

    The most specialized of equally good function templates is preferred, a nontemplate function is preferred over equally good matches to a function template.

Overloaded Templates and Conversions

  • Three debug_rep functions are viable:

    debug_rep(const T&)

    with T bound to char[10]

    debug_rep(T*)

    with T bound to const char

    debug_rep(const string&)

    requires a conversion from const char* to string
  • Both templates provide an exact match to the argument: the second template requires a permissible conversion from array to pointer, and this conversion is considered as an exact match for function-matching purposes(6.6.1).

    The nontemplate version is viable but requires a user-defined conversion. So, this function is less good than an exact match, leaving the two templates as the possible functions to call.

    As before, the T* version is more specialized and is the one that will be selected.

  • If we want to handle character pointers as strings, we can define two more nontemplate overloads:
// convert the character pointers to string and call the string version of debug_rep
string debug_rep(char *p)
{
    return debug_rep(string(p));
}
string debug_rep(const char *p)
{
    return debug_rep(string(p));
}
           

Missing Declarations Can Cause the Program to Misbehave

  • To make the char* version of debug_rep work, a declaration for

    debug_rep(const string&)

    must be in scope when these functions are defined; otherwise, the wrong version of debug_rep will be called:
template <typename T> string debug_rep(const T &t);
template <typename T> string debug_rep(T *p);
// the following declaration must be in scope
// for the definition of debug_rep(char*) to do the right thing
string debug_rep(const string &);
string debug_rep(char *p)
{
    // if the declaration for the version that takes a const string& is not in scope
    // the return will call debug_rep(const T&) with T instantiated to string
    return debug_rep(string(p));
}
           
  • Ordinarily, if we use a function that we don’t declare, our code won’t compile. Not so with functions that overload a template function.

    If the compiler can instantiate the call from the template, then the missing declaration won’t matter. If we forget to declare the version of debug_rep that takes a string, the compiler will instantiate the template version that takes a const T&.

  • Declare every function in an overload set before you define any of the functions. In this way, you needn’t worry whether the compiler will instantiate a call before it sees the function you intended to call.

##Exercises Section 16.3(Omit: strange techniques!)

16.4. Variadic Templates

  • A variadic template is a template class or function that can take a varying number of parameters. The varying parameters are known as a parameter pack. Two kinds of parameter packs:
    1. A template parameter pack represents zero or more template parameters,
    2. A function parameter pack represents zero or more function parameters.
  • We use an ellipsis to indicate that a template or function parameter represents a pack. In a template parameter list,

    class...

    or

    typename...

    indicates that the following parameter represents a list of zero or more types; the name of a type followed by an ellipsis represents a list of zero or more nontype parameters of the given type.
  • In the function parameter list, a parameter whose type is a template parameter pack is a function parameter pack.
// 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);
           
  • foo is a variadic function that has one type parameter T and a template parameter pack Args that represents zero or more additional type parameters.

    foo’ s parameter list has one parameter, whose type is const& to whatever type T has, and a function parameter pack rest that represents zero or more function parameters.

  • The compiler deduces the template parameter types from the functions arguments. For a variadic template, the compiler also deduces the number of parameters in the pack.
int i = ;
double d = ;
string s = "how";
foo(i, s, , d);   // three parameters in the pack
foo(s, , "hi");   // two parameters in the pack
foo(d, s);      // one parameter in the pack
foo("hi");      // empty pack
           
  • The compiler will instantiate four different instances of foo:
void foo(const int&, const string&, const int&, const double&);
void foo(const string&, const int&, const char[]&);
void foo(const double&, const string&);
void foo(const char[]&);
           
  • The type of T is deduced from the type of the first argument; the remaining arguments(if any) provide the number of, and types for, the additional arguments to the function.

The sizeof… Operator

  • Use

    sizeof...

    operator to know how many elements there are in a pack. Like sizeof (4.9),

    sizeof...

    returns a constant expression(2.4.4) and does not evaluate its argument:
template<typename ... Args>
void g(Args ... args)
{
    cout << sizeof...(Args) << endl;    // number of type parameters
    cout << sizeof...(args) << endl;    // number of function parameters
}
           

Exercises Section 16.4

Exercise 16.51

Determine what sizeof…(Args) and sizeof…(rest) return for each call to foo in this section.
  • See 16.52.

Exercise 16.52

Write a program to check your answer to the previous question.
#include <iostream>

using namespace std;

template<typename T, typename ... Args>
void foo(const T &t, const Args& ... rest)
{
    cout << "Args number = " << sizeof...(Args) << '\n';
    cout << "rest number = " << sizeof...(rest) << '\n';
}

int main()
{
    int i = ;
    double d = ;
    string s = "how now brown cow";
    foo(i, s, , d); // three parameters in the pack
    foo(s, , "hi"); // two parameters in the pack
    foo(d, s); // one parameter in the pack
    foo("hi"); // empty pack
}
/*
Args number = 3
rest number = 3
Args number = 2
rest number = 2
Args number = 1
rest number = 1
Args number = 0
rest number = 0
*/
           

16.4.1. Writing a Variadic Function Template

  • Variadic functions are used when we know neither the number nor the types of the arguments.

    Variadic functions are often recursive(6.3.2): the first call processes the first argument in the pack and calls itself on the remaining arguments. To stop the recursion, we need a non-variadic function.

// 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 print version 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
}
           
  • A declaration for the nonvariadic version must be in scope when the variadic version is defined; otherwise, the variadic function will recurse indefinitely.
    1. The first version stops the recursion and prints the last argument in the initial call to print.
    2. The second, variadic, version prints the argument bound to t and calls itself to print the remaining values in the function parameter pack.

      (1) The first argument in rest is bound to t in the next call.

      (2) The remaining arguments in rest form the parameter pack for the next call to print. So, on each call, the first argument in the pack is removed from the pack and becomes the argument bound to t.

    1. All but the last calls match only the variadic version because the nonvariadic version isn’t viable(number of parameters not enough).
    2. For the last call, both versions provide an equally good match for the call(a parameter pack can be empty). Since a nonvariadic template is more specialized than a variadic template, the nonvariadic version is chosen(16.3).

Exercises Section 16.4.1

Exercise 16.53

Write your own version of the print functions and test them by printing one, two, and five arguments, each of which should have different types.
#include <iostream>

using namespace std;

template<typename T>
ostream &print(ostream &os, const T &t)
{
    return os << t << '\n';
}
template <typename T, typename... Args>
ostream &print(ostream &os, const T &t, const Args&... rest)
{
    os << t << ", ";
    return print(os, rest...);
}

int main()
{
    int obj1 = ;
    double obj2 = ;
    string obj3 = "gaoxiang";
    char obj4 = 'X';
    bool obj5 = false;

    print(cout, obj1);
    print(cout, obj1, obj2);
    print(cout, obj1, obj2, obj3, obj4, obj5);
}
/*
0
0, 3.14
0, 3.14, gaoxiang, X, 0
*/
           

Exercise 16.54

What happens if we call print on a type that doesn’t have an << operator?
  • Use vector. compile error: cannot bind ‘std::ostream {aka std::basic_ostream}’ lvalue to ‘std::basic_ostream&&’|

Exercise 16.55

Explain how the variadic version of print would execute if we declared the nonvariadic version of print after the definition of the variadic version.
  • error: recursively required from ‘std::ostream& print(std::ostream&, const T&, const Args& …) [with T = double; Args = {std::basic_string

16.4.2. Pack Expansion

  • 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.
template <typename T, typename... Args>
ostream &print(ostream &os, const T &t, const Args&... rest) // expand Args
{
    os << t << ", ";
    return print(os, rest...); // expand rest
}
           
  • The first expansion expands the template parameter pack and generates the function parameter list for print.

    The expansion of Args applies the pattern

    const Args&

    to each element in the template parameter pack Args. The expansion of this pattern is a comma-separated list of zero or more parameter types, each of which has the form const type&.

    print(cout, i, s, 42); // two parameters in the pack

    The types of the last two arguments and the pattern determine the types of the trailing parameters. This call is instantiated as

    ostream& print(ostream&, const int&, const string&, const int&);

  • The second expansion appears in the call to print and generates the argument list for the call to print.

    The second expansion happens in the recursive call to print. The pattern is the name of the function parameter pack(i.e., rest). This pattern expands to a comma-separated list of the elements in the pack. Thus, this call is equivalent to

    print(os, s, 42);

Understanding Pack Expansions

// call debug_rep on each argument in the call to print
template <typename... Args>
ostream &error_msg(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)

    that says we want to call debug_rep on each element in the function parameter pack rest. The resulting expanded pack will be a comma-separated list of calls to debug_rep.
// passes the pack to debug_rep; print(os, debug_rep(a1, a2, ..., an))
print(os, debug_rep(rest...)); // error: no matching function to call
           
  • The problem is that we expanded rest in the call to debug_rep. This call would execute as if we had written

    print(cerr, debug_rep(a1, a2, ..., an));

    Since the debug_rep function is not variadic, there is match version of debug_rep.

Exercises Section 16.4.2

Exercise 16.56

Write and test a variadic version of error_msg.
#include <iostream>
#include <sstream>

using namespace std;

// always declare first:
template <typename T>
string debug_rep(const T& t);
template <typename T>
string debug_rep(T* p);

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

// print any type we don't otherwise.
template<typename T>
string debug_rep(const T& t)
{
    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>
string debug_rep(T* p)
{
    ostringstream ret;
    ret << "pointer: " << p;

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

    return ret.str();
}

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

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

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

template<typename T>
ostream& print(ostream& os, const T& t)
{
    return os << t;
}

template<typename T, typename... Args>
ostream& print(ostream &os, const T &t, const Args&... rest)
{
    // print the first argument
    os << t << ",";
    // recursive call; print the other arguments
    return print(os, rest...);
}

// call debug_rep on each argument in the call to print
template<typename... Args>
ostream& error_msg(ostream& os, const Args... rest)
{
    return print(os, debug_rep(rest)...);
}

int main()
{
    error_msg(cout, , , , , , "gao", "xiang");
    return ;
}
           

Exercise 16.57

Compare your variadic version of error_msg to the error_msg function in 6.2.6. What are the advantages and disadvantages of each approach?
  • The error_msg takes initializer_list as the argument. So only the elements stored in it must be the same or at least convertible. In contrast, the variadic version provides better flexibility.

16.4.3. Forwarding Parameter Packs

  • Under C++11, we can use variadic templates together with forward to write functions that pass their arguments unchanged to some other function. We add emplace_back member to StrVec class(13.5). The emplace_back member of the library containers is a variadic member template(16.1.4) that uses its arguments to construct an element directly in space managed by the container.
  • emplace_back for StrVec has to be variadic because string has constructors that differ in terms of their parameters. Because we’d like to be able to use the string move constructor, we need to preserve all the type information about the arguments passed to emplace_back.
  • Preserving type information is a two-step process. First, to preserve type information in the arguments, we must define emplace_backs function parameters as rvalue references to a template type parameter(16.2.7).
class StrVec
{
public:
    template <class... Args> void emplace_back(Args&&...);
    // remaining members as in § 13.5 (p. 526)
};
           
  • The pattern in the expansion of the template parameter pack, &&, means that each function parameter will be an rvalue reference to its corresponding argument.
  • Second, we must use forward to preserve the arguments original types when emplace_back passes those arguments to construct(16.2.7).
template <class... Args>
inline void StrVec::emplace_back(Args&&... args)
{
    chk_n_alloc(); // reallocates the StrVec if necessary
    alloc.construct(first_free++, std::forward<Args>(args)...);
}
           
  • emplace_back calls chk_n_alloc(13.5) to ensure that there is enough room for an element and calls construct to create an element in the first_free spot. The expansion in the call to construct:

    std::forward<Args>(args)...

    expands both the template parameter pack, Args, and the function parameter pack, args. This pattern generates elements with the form

    std::forward<Ti>(ti)

    where Ti represents the type of the ith element in the template parameter pack and ti represents the ith element in the function parameter pack.
  • Assume svec is a StrVec, if we call

    svec.emplace_back(10, '0'); // adds 0000000000 as a new last element

    the pattern in the call to construct will expand to

    std::forward(10), std::forward(c)

  • By using forward, we guarantee that if emplace_back is called with an rvalue, then construct will also get an rvalue. For example, in this call:

    svec.emplace_back(s1 + s2); // uses the move constructor

    the argument to emplace_back is an rvalue, which is passed to construct as

    std::forward<string>(string("the end"))

    The result type from forward is string&&, so construct will be called with an rvalue reference. The construct function will, in turn, forward this argument to the string move constructor to build this element.

Advice: Forwarding and Variadic Templates

  • Variadic functions often forward their parameters to other functions. Such functions typically have following form:
// 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 does the real work of the function. The expansion in the call to work expands both the template parameter pack and the function parameter pack.
  • Because the parameters to fun are rvalue references, we can pass arguments of any type to fun; because we use std::forward to pass those arguments, all type information about those arguments will be preserved in the call to work.

Exercises Section 16.4.3

Exercise 16.58

Write the emplace_back function for your StrVec class and for the Vec class that you wrote for the exercises in 16.1.2(p. 668).
  • Omit.

Exercise 16.59

Assuming s is a string, explain svec.emplace_back(s).
  • Omit.

Exercise 16.60

Explain how make_shared(12.1.1, p. 451) works.
  • make_shared should be a variadic template function that forwards all arguments to underlying constructors that allocate and initializes an object in dynamic memory, and at last, build a shared_ptr by wrapping the raw pointer.

Exercise 16.61

Define your own version of make_shared.
#include <iostream>
#include <memory>
#include <string>

namespace ch16
{
template <typename T, typename ... Args>
auto make_shared(Args&&... args) -> std::shared_ptr<T>
{
    return std::shared_ptr<T>(new T(std::forward<Args>(args)...));
}
}

struct Foo
{
    explicit Foo(int b) : bar(b) { }
    int bar;
};

int main()
{
    auto num = ch16::make_shared<int>();
    std::cout << *num << std::endl;

    auto str = ch16::make_shared<std::string>(, '0');
    std::cout << *str << std::endl;

    auto foo = ch16::make_shared<Foo>();
    std::cout << foo->bar << std::endl;

    return ;
}
           

16.5. Template Specializations

Defining a Function Template Specialization

  • A specialization is a separate definition of the template in which one or more template parameters have particular types. When we specialize a function template, we must supply arguments for every template parameter in the original template. We use

    template <>

    to indicate that we are specializing a template.
// special version of compare to handle pointers to character arrays
template <>
int compare(const char* const &p1, const char* const &p2)
{
    return strcmp(p1, p2);
}
           
  • When we define a specialization, the function parameter type(s) must match the corresponding types in a previously declared template. Here we specialize:

    template <typename T> int compare(const T&, const T&);

    in which the function parameters are references to a const type. We want to define a specialization with T as const char*. Our function requires a reference to the const version of this type. The const version of a pointer type is a constant pointer. The type we need to use is const char* const &, which is a reference to a const pointer to const char.

Function Overloading versus Template Specializations

  • Specializations instantiate a template; they do not overload it.

    Specializations do not affect function matching.

    Whether we define a particular function as a specialization or as an independent, nontemplate function can impact function matching.

// first version; can compare any two types
template <typename T> int compare(const T&, const T&);
// second version to handle string literals
template<size_t N, size_t M>
int compare(const char (&)[N], const char (&)[M]);
           
  • For example, we define two versions of compare function template, one takes references to array parameters and the other that takes const T&. The fact that we have a specialization for character pointers has no impact on function matching. When we call compare on a string literal:

    compare("hi", "mom")

    both function templates are viable and provide an exact match to the call. But the version with character array parameters is more specialized(16.3) and is selected.
  • If we define the version of compare that takes character pointers as a plain nontemplate function, there would be three viable functions: the two templates and the nontemplate character-pointer version. All three are equally good matches for this call. When a nontemplate provides an equally good match as a function template, the nontemplate is selected(16.3)

Key Concept: Ordinary Scope Rules Apply to Specializations

  • In order to specialize a template, a declaration for the original template must be in scope.

    A declaration for a specialization must be in scope before any code uses that instantiation of the template.

  • With ordinary classes and functions, the compiler can’t process our code when missing declarations. But when a specialization declaration is missing, the compiler will generate code using the original template. Though it is an error for a program to use a specialization and an instantiation of the original template with the same set of template arguments, the compiler can’t detect.
  • Templates and their specializations should be declared in the same header file. Declarations for all the templates with a given name should appear first, followed by any specializations of those templates.

Class Template Specializations

  • We define a specialization of the library hash template that we can use to store Sales_data objects in an unordered container. By default, the unordered containers use hash(11.4) to organize their elements. To use this default with our own data type, we must define a specialization of the hash template. A specialized hash class must define

    An overloaded call operator(14.8) that returns a size_t and takes an object of the containers key type.

    Two type members, result_type and argument_type, which are the return and argument types, respectively, of the call operator

    The default constructor and a copy-assignment operator(which can be implicitly defined(13.1.2))

  • When we specialize a template, we must do so in the same namespace in which the original template is defined. We can add members to a namespace. To do so, we must first open the namespace:
// open the std namespace so we can specialize std::hash
namespace std {
}// close the std namespace; note: no semicolon after the close curly
           
  • Any definitions that appear between the open and close curlies will be part of the std namespace. The following defines a specialization of hash for Sales_data:
//open the std namespace so we can specialize std::hash
namespace std
{
template <> // we're defining a specialization with
struct hash<Sales_data> // the template parameter of Sales_data
{
    // the type used to hash an unordered container must define these types
    typedef size_t result_type;
    typedef Sales_data argument_type; // by default, this type needs ==
    size_t operator()(const Sales_data& s) const;
    // our class uses synthesized copy control and default constructor
};
size_t hash<Sales_data>::operator()(const Sales_data& s) const
{
    return hash<string>()(s.bookNo) ^
           hash<unsigned>()(s.units_sold) ^
           hash<double>()(s.revenue);
}
} // close the std namespace; note: no semicolon after the close curly
           
  • Our hash definition starts with template<>, which indicates that we are defining a fully specialized template. The template we’re specializing is named hash and the specialized version is hash. The members of the class follow directly from the requirements for specializing hash.
  • As with any other class, we can define the members of a specialization inside the class or out of it. The overloaded call operator must define a hashing function over the values of the given type. This function is required to return the same result every time it is called for a given value.
  • The library defines specializations of the hash class for the built-in types and for many of the library types. We use an unnamed hash object to generate a hash code for bookNo, an object of type hash to generate a hash from units_sold, and an object of type hash to generate a hash from revenue. We exclusive OR(4.8) these results to form an overall hash code for the given Sales_data object.
  • It is worth noting that we defined our hash function to hash all three data members so that our hash function will be compatible with our definition of operator== for Sales_data(14.3.1, p. 561). By default, the unordered containers use the specialization of hash that corresponds to the key_type along with the equality operator on the key type.
  • Assuming our specialization is in scope, it will be used automatically when we use Sales_data as a key to one of these containers:
// uses hash<Sales_data> and Sales_data operator==from § 14.3.1 (p. 561)
unordered_multiset<Sales_data> SDset;
           
  • Because hash uses the private members of Sales_data, we must make this class a friend of Sales_data:
template <class T> class std::hash; // needed for the friend declaration
class Sales_data
{
    friend class std::hash<Sales_data>;
    // other members as before
};
           
  • Here we say that the specific instantiation of hash is a friend. Because that instantiation is defined in the std namespace, we must remember to that this hash type is defined in the std namespace. Hence, our friend declaration refers to std::hash.
  • To enable users of Sales_data to use the specialization of hash, we should define this specialization in the Sales_data header.

Class-Template Partial Specializations

  • Differently from function templates, a class template specialization does not have to supply an argument for every template parameter. We can specify some, but not all, of the template parameters or some, but not all, aspects of the parameters. A class template partial specialization is itself a template. Users must supply arguments for those template parameters that are not fixed by the specialization.
  • We can partially specialize only a class template. We cannot partially specialize a function template.
  • In 16.2.3(p. 684) we introduced the library remove_reference type. That template works through a series of specializations:
// original, most general template
template <class T>
struct remove_reference
{
    typedef T type;
};
// partial specializations that will be used for lvalue and rvalue references
template <class T>
struct remove_reference<T&> // lvalue references
{
    typedef T type;
};
template <class T> struct remove_reference<T&&> // rvalue references
{
    typedef T type;
};
           
  • The first template defines the most general version. It can be instantiated with any type; it uses its template argument as the type for its member named type. The next two classes are partial specializations of this original template.
  • Because a partial specialization is a template, we start, as usual, by defining the template parameters. Like any other specialization, a partial specialization has the same name as the template it specializes. The specializations template parameter list includes an entry for each template parameter whose type is not completely fixed by this partial specialization. After the class name, we specify arguments for the template parameters we are specializing. These arguments are listed inside angle brackets following the template name. The arguments correspond positionally to the parameters in the original template.
  • The template parameter list of a partial specialization is a subset of, or a specialization of, the parameter list of the original template. In this case, the specializations have the same number of parameters as the original template. However, the parameters type in the specializations differ from the original template. The specializations will be used for lvalue and rvalue reference types, respectively:
int i;
// decltype(42) is int, uses the original template
remove_reference<decltype()>::type a;
// decltype(i) is int&, uses first (T&) partial specialization
remove_reference<decltype(i)>::type b;
// decltype(std::move(i)) is int&&, uses second (i.e., T&&) partial specialization
remove_reference<decltype(std::move(i))>::type c;
           
  • All three variables, a, b, and c, have type int.

Specializing Members but Not the Class

  • Rather than specializing the whole template, we can specialize just specific member function(s). For example, if Foo is a template class with a member Bar, we can specialize just that member:
template <typename T> struct Foo
{
    Foo(const T &t = T()): mem(t) { }
    void Bar()
    {
        /* ... */
    }
    T mem;
    // other members of Foo
};
template<> // we're specializing a template
void Foo<int>::Bar() // we're specializing the Bar member of Foo<int>
{
    // do whatever specialized processing that applies to ints
}
           
  • Here we are specializing just one member of the Foo class. The other members of Foo will be supplied by the Foo template:
Foo<string> fs; // instantiates Foo<string>::Foo()
fs.Bar();           // instantiates Foo<string>::Bar()
Foo<int> fi;        // instantiates Foo<int>::Foo()
fi.Bar();           // uses our specialization of Foo<int>::Bar()
           
  • When we use Foo with any type other than int, members are instantiated as usual. When we use Foo with int, members other than Bar are instantiated as usual. If we use the Bar member of Foo, then we get our specialized definition.

##Exercises Section 16.5(Omit)

Chapter Summary

Please indicate the source: http://blog.csdn.net/gaoxiangnumber1

Welcome to my github: https://github.com/gaoxiangnumber1