C++类模板

2023-11-14

1. 定义类模板

        程序清单类模板-1列出了类模板和成员函数模板,明确这些模板不是类和成员函数定义很重要,因为它们是C++编译指令,说明了如何生成类和成员函数定义。

        不能将模板成员函数放在独立的实现文件中,由于模板不是函数,它们不能单独编译,模板必须与特定的模板实例化请求一起使用。因此,最简单的方式是将所有模板信息放在一个头文件中,并再要使用这些模板的文件中包含该头文件。

//程序清单  类模板-1
//stacktp.h
#ifndef STACKTP_H_
#define STACKTP_H_

template<class Type>
class Stack
{
private:
    enum {MAX = 10};           //Stack类拥有的常量
    Type items[MAX];
    int top;

public:
    Stack();
    bool isempty();
    bool isfull();
    bool push(const Type& item);    //add item to stack
    bool pop(Type& item);           //pop top into item
};

template<class Type>
Stack<Type>::Stack()
{
    top = 0;
}

template<class Type>
bool Stack<Type>::isempty()
{
    return top == 0;
}

template<class Type>
bool Stack<Type>::isfull()
{
    return top == MAX;
}

template<class Type>
bool Stack<Type>::push(const Type& item)
{
    if(top < MAX)
    {
        items[top++] = item;
        return true;
    }
    else
        return false;
}

template<class Type>
bool Stack<Type>::pop(Type& item)
{
    if(top > 0)
    {
        item = items[--top];
        return true;
    }
    else
        return false;
}

#endif

注意模板成员函数定义与类模板声明一起放在模板的头文件中。

2. 使用模板类

        仅在程序中包含模板并不能生成模板类,必须请求实例化,必须显式地提供所需的类型。

Stack<int> kernels;
Stack<std::string> colonels;

3. 深入探讨模板类

        正确使用指针栈的方法之一是,让调用程序提供一个指针数组,其中每个指针都指向不同的字符串。注意,栈的任务是管理指针,不是创建指针。程序清单类模板-2重新定义了Stack<Type>类,使Stack构造函数能够接受一个可选大小的参数。这涉及到在内部使用动态数组,因此Stack类需要包含一个析构函数、一个复制构造函数和一个赋值运算符。

//程序清单 类模板-2
//stcktp1.h

#ifndef STCKTP1_H_
#define STCKTP1_H_

template<class Type>
class Stack
{
private:
    enum {SIZE = 10};  //默认大小
    int stacksize;
    Type* items;
    int top;

public:
    explicit Stack(int ss = SIZE);
    Stack(const Stack& st);
    ~Stack() { delete[] items; }
    bool isempty() { return top == 0; }
    bool isfull() { return top == stacksize; }
    bool push(const Type& item);
    bool pop(Type& item);
    Stack& operator=(const Stack& st);
};

template<class Type>
Stack<Type>::Stack(int ss): stacksize(ss), top(0)
{
    item = new Type[stacksize];
}

template<class Type>
Stack<Type>::Stack(const Stack<Type>& st)
{
    stacksize = st.stacksize;
    top = st.top;
    items = new Type[stacksize];
    for(int i = 0; i < top; i++)
    {
        items[i] = st.items[i];
    }
}

template<class Type>
bool Stack<Type>::push(const Type& item)
{
    if(top < stacksize)
    {
        items[top++] = item;
        return true;
    }
    return false;
}

template<class Type>
bool Stack<Type>::pop(Type& item)
{
    if(top > 0)
    {
        item = items[--top];
        return true;
    }
    return false;
}

template<class Type>
Stack<Type>& Stack<Type>::operator=(const Stack<Type>& st)
{
    if(this == &st)
        return *this;
    delete[] items;
    stacksize = st.stacksize;
    top = st.top;
    items = new Type[stacksize];
    for(int i = 0; i < top; i++)
    {
        items[i] = st.items[i];
    }
    return *this;
}

#endif

程序清单类模板-3使用了这个Stack模板类。

//程序清单 类模板-3
//stkoptr1.cpp

#include <iostream>
#include <cstdlib>
#include <ctime>
#include "stcktp1.h"
const int Num = 10;

int main()
{
    std::srand(std::time(0));
    std::cout << "Please enter stack size: ";
    int stacksize;
    std::cin >> stacksize;

    //create an empty stack with stacksize siots
    Stack<const char*> st(stacksize);

    //in basket
    const char* in[Num] = {
        " 1: Hank Gilgamesh", " 2: Kiki Ishtar",
        " 3: Betty Rocker", " 4: Ian Flagranti",
        " 5: Wolfgang Kibble", " 6: Portia Koop",
        " 7: Joy Almondo", " 8: Xaverie Paprika",
        " 9: Juan Moore", " 10: Misha Mache"
    };

    //out basket
    const char* out[Num];           //类型为const char*,因为指针数组被初始化为一组字符串常量

    int processed = 0;
    int nextin = 0;
    while(processed < Num)
    {
        if(st.isempty())
            st.push(in[nextin++]);
        else if(st.isfull())
            st.pop(out[processed++]);
        else if(std::rand() % 2 && nextin < Num)
            st.push(in[nextin++]);
        else
            st.pop(out[processed++]);
    }

    for(int i = 0; i < Num; i++)
    {
        std::cout << out[i] << std::endl;
    }
    std::cout << "Bye\n";

    return 0;
}

4. 数组模板示例和非类型参数

        模板常用作容器类。

        自己设计一个允许指定数组大小的简单数组模板。一种方法是在类中使用动态数组和构造函数参数来提供元素数目,例如程序清单类模板-2;另一种方法是使用模板参数来提供常规数组的大小,C++11新增的模板array就是这样做的,程序清单类模板-4演示了如何做。

//程序清单 类模板-4
//arraytp.h

#ifndef ARRAYTP_H_
#define ARRAYTP_H_

#include <iostream>
#include <cstdlib>

template<class T, int n>
class ArrayTP
{
private:
    T ar[n];
public:
    ArrayTP() {};
    explicit ArrayTP(const T& v);
    virtual T& operator[] (int i);
    virtual T operator[](int i) const;
};

template<class T, int n>
ArrayTP<T, n>::ArrayTP(const T& v)
{
    for(int i = 0; i < n; i++)
        ar[i] = v;
}

template<class T, int n>
T& ArrayTP<T, n>::operator[](int i)
{
    if(i < 0 || i >= n)
    {
        std::cerr << "Error in array limits: " << i << " is out of range\n";
        std::exit(EXIT_FAILURE);
    }
    return ar[i];
}

template<class T, int n>
T ArrayTP<T, n>::operator[](int i) const
{
    if(i < 0 || i >= n)
    {
        std::cerr << "Error in array limits: " << i << " is out of range\n";
        std::exit(EXIT_FAILURE);
    }
    return ar[i];
}

#endif

        关键字class指出T为类型参数,int指出n的类型为int。这种参数(指定特殊的类型而不是用作泛型名)称为非类型(non-type)或表达式(expression)参数。

        表达式参数有一些限制,表达式参数可以是整形、枚举、引用或指针。因此double m是不合法的,但double& rm和double* pm是合法的。另外,模板代码不能改变参数的值,也不能使用参数的地址。所以在ArrayTP模板中不能使用诸如n++和&n等表达式。另外,实例化模板时,用作表达式参数的值必须是常量表达式。

5. 模板的多功能性

        可以将用于常规类的技术用于模板类。模板类可以用作基类,也可用作组件类,还可用作其他模板的类型参数。例如,可以使用数组模板实现栈模板,也可以使用数组模板来构造数组——数组元素是基于栈模板的栈。即可以编写如下代码:

template<typename T>
class Array
{
private:
    T entry;
    //...
};

template<typename Type>
class GrowArray: public Array<Type>
{
    //...               //inheritance
}

template<typename Tp>
class Stack
{
    Array<Tp> ar;       //use an Array<> as a component
}

//...
Array< Stack<int> > assi;      // an array of stacks of int

5.1 递归使用模板

        可以递归使用模板,例如对于前面的数组模板定义,可以这样使用它:

ArrayTP< ArrayTP<int, 5>, 10> twodee;

        与之等价的常规数组声明如下:

int twodee[10][5]

5.2 使用多个类型参数

        模板可以包含多个类型参数。例如,假设希望类可以保存两种值,则可以创建并使用Pair模板来保存两个不同的值(标准模板库提供了类似的模板,名为pair)。程序清单类模板-5所示的小程序是一个这样的示例。其中,方法first() const和second() const报告存储的值。由于这两个方法返回Pair数据成员的引用,因此可以通过赋值重新设置存储的值。

//程序清单  类模板-5
//pairs.cpp -- defining and using a Pair template
#include <iostream>
#include <string>
template<class T1, class T2>
class Pair
{
private:
    T1 a;
    T2 b;
public:
    T1& first();
    T2& second();
    T1 first() const { return a; }
    T2 second() const { return b; }
    Pair(const T1& aval, const T2& bval): a(aval), b(bval) {}
    Pair() {}
};

template<class T1, class T2>
T1& Pair<T1, T2>::first()
{
    return a;
}

template<class T1, class T2>
T2& Pair<T1, T2>::second()
{
    return b;
}

int main()
{
    using std::cout;
    using std::endl;
    using std::string;
    Pair<string, int> ratings[4] = 
    {
        Pair<string, int>("The Purpled Duck", 5),
        Pair<string, int>("Jaquie's Frisco Al Fresco", 4),
        Pair<string, int>("Cafe Souffle", 5),
        Pair<string, int>("Bertie's Eats", 3)
    };

    int joints = sizeof(ratings) / sizeof(Pair<string, int>);  //必须用Pair<string, int>来调用构造函数,并将它作为sizeof的参数,因为类名是Pair<string, int>,而不是Pair
    cout << "Rating:\t Batery\n";
    for(int i = 0; i < joins; i++)
        cout << ratings[i].second() << ":\t"
        << ratings[i].first() << endl;
    cout << "Oops! Revised rating:\n";
    ratings[3].first() = "Bertie's Fab Eats";
    ratings[3].second() = 6;
    cout << rating[3].second() << ":\t "
        << ratings[3].first() << endl;

    return 0;
}

5.3 默认类型模板参数

        类模板的另一项新特性是,可以为类型参数提供默认值:

template<class T1, class T2 = int>
class Topo
{
//...
};

        这样,如果省略了T2的值,编译器将使用int。标准模板库经常使用该特性,将默认类型设置为类。虽然可以为类模板类型参数提供默认值,但不能为函数模板参数提供默认值。然而,可以为非类型参数提供默认值,这对于类模板和函数模板都是适用的。

6. 模板的具体化

        类模板与函数模板很相似,因为可以有隐式实例化、显式实例化和显式具体化。它们统称为具体化(specialization)。模板以泛型的方式描述类,而具体化是使用具体的类型生成类声明

6.1 隐式实例化

        目前为止,本文章所有的模板示例使用的都是隐式实例化,即它们声明一个或多个对象,指出所需的类型,而编译器使用通用模板提供的说明生成具体的类定义。

ArrayTP<int, 100> stuff;   //implicit instantiation

        编译器在需要对象之前,不会生成类的隐式实例化

ArrayTP<double, 30> *pt;   //仅声明一个指针,不需要实际对象
pt = new ArrayTP<double, 30>;   //现在需要一个实际对象

        第二条语句导致编译器生成类定义,并根据该定义创建一个对象。

6.2 显式实例化

        当使用关键字template并指出所需类型来声明类时,编译器将生成类声明的显式实例化。声明必须位于模板定义所在的名称空间中。

template class ArrayTP<string, 100>;

        此时,虽然没有创建类对象,编译器也将生成类声明(包括方法定义)。和隐式实例化一样,也将根据通用模板来生成具体化。

6.3 显式具体化

        显式具体化是特定类型(用于替换模板中的泛型)的定义。有时候,可能需要在为特殊类型实例化时,对模板进行修改,使其行为不同。此时可以创建显式具体化。例如,假设已经为用于表示排序后数组的类(元素在加入时被排序)定义了一个模板:

template <typename T>
class SortedArray
{
    //details omitted
};

        另外,假设模板使用>运算符来对值进行比较。对于数字,这样管用。如果T表示一种类,则只要定义了T::operator>()方法,这也管用。但如果T是const char*字符串,将不管用。如果强行使用此模板,则字符串将按地址(按照字母顺序)排序。这要求类定义使用strcmp(),而不是>来对值进行比较。此时可以提供一个显式模板具体化,这将采用为具体类型定义的模板,而不是为泛型定义的模板。当具体化模板和通用模板都与实例化请求匹配时,编译器将使用具体化版本。

        具体化类模板定义的格式:

template <> class SortedArray<const char*>
{
    // details omitted
};

         其中的实现代码将使用strcmp()(而不是>)来比较数组值。现在,当请求const char*类型的SortedArray模板时,编译器将使用上述专用的定义,而不是通用的模板定义。

6.4 部分具体化

        C++还允许部分具体化(partial specialization),即部分限制模板的通用性。

//模板声明
template<class T1, class T2> class Pair
{
};

//部分具体化:将T2限制为int
template<class T1> class Pair<T1, int>
{
};

        注意,如果指定所有的类型,则<>内将为空,这将导致显示具体化:

// specialization with T1 and T2 set to int
template<> class Pair<int, int>
{
};

        也可以通过为指针提供特殊版本来部分具体化现有的模板:

template<class T>
class Feeb
{
};

template<class T*>
class Feeb
{
};

7. 成员模板

        模板可用作结构、类或模板类的成员。要完全实现STL的设计,必须使用这项特性。

// 程序清单 类模板-6
// 该程序清单提供一个简单的模板类示例,该模板类将另一个模板类和模板函数作为其成员
// tempmemb.cpp -- template members

#include <iostream>
using std::cout;
using std::endl;
template<typename T>
class beta
{
private:
    template<typename V>
    class hold
    {
    private:
        V val;
    public:
        hold(V v = 0): val(v) {}
        void show() const { cout << val << endl; }
        V Value() const { return val; }
    };
    hold<T> q;        // template object
    hold<int> n;      // template object

public:
    beta(T t, int i): q(t), n(i) {}
    template<typename U>
    U blab(U u, T t) { return (n.Value() + q.Value()) * u / t; }
    void Show() const { q.show(); n.show(); }
};

int main()
{
    beta<double> guy(3.5, 3);
    cout << "T was set to double\n";
    guy.Show();
    cout << "V was set to T, which is double, then V was set to int\n";
    cout << guy.blab(10, 2.3) << endl;
    cout << "U was set to int\n";
    cout << guy.blab(10.0, 2.3) << endl;
    cout << "U was set to double\n";
    cout << "Done\n";
    
    return 0;
}

        注意:blab()方法的U类型由该方法被调用时的参数值显式确定,T类型由对象的实例化类型确定。在这个例子中,guy的声明将T的类型设置为double,而下述方法调用的第一个参数将U的类型设置为int(参数10对于的类型):

cout << guy.blab(10, 2.5) << endl;

        因此,虽然混合类型引起的自动类型转换导致blab()中的计算以double类型进行,但返回值的类型为U(即int),因此它被截断为28。

        可以在beta模板中声明hold类和blab方法,并在beta模板的外面定义它们。如果所用的编译器接受类外面的定义,则在beta模板之外定义模板方法的代码如下:

template<typename T>
class beta
{
private:
    template<typename V> class hold;
    hold<T> q;
    hold<int> n;

public:
    beta(T t, int i): q(t), n(i) {}
    template<typename U>
    U blab(U u, T t);
    void Show() const { q.show(); n.show(); }
};

//成员定义
template<typename T>
    template<typename V>
        class beta<T>::hold
        {
        private:
            V val;
        public:
            hold(V v = 0): val(v) {}
            void show() const { std::cout << val << std::endl(); }
            V Value() const { return val; }
        };
template<typename T>
    template<typename U>
        U beta<T>::blab(U u, T t)
        {
            return (n.Value() + q.Value()) * u / t; 
        }

        因为模板是嵌套的,必须使用下面的语法:

template<typename T>
    template<typename V>

        定义还必须指出hold和blab是beta<T>类的成员。这是通过使用作用域解析运算符来完成的。

8. 将模板用作参数

        模板可以包含类型参数(如typename T)和非类型参数(如int n)。模板还可以包含本身就是模板的参数,这种参数是模板新增的特性,用于实现STL。

// 程序清单 - 类模板-7  将模板用作参数
// tempparm.cpp - templates as parameters

#include <iostream>
#include "stacktp.h"

template<template<typename T> class Thing>
class Crab
{
private:
    Thing<int> s1;
    Thing<double> s2;

public:
    Crab() {}
    // assumes the Thing class has push() and pop() members
    bool push(int a, double x) { return s1.push(a) && s2.push(x); }
    bool pop(int& a, double& x) { return s1.pop(a) && s2.pop(x); }
};

int main()
{
    using std::cout;
    using std::cin;
    using std::endl;
    Crab<Stack> nebula;
    // Stack class must match template<typename T> class Thing
    int ni;
    double nb;
    cout << "Enter int double pairs, such as 4 3.5 (0 0 to end):\n";
    while(cin >> ni >> nb && ni > 0 && nb > 0)
    {
        if(!nebula.push(ni, nb))
            break;
    }
    while(nebula.pop(ni, nb))
        cout << ni << ", " << nb << endl;
    cout << "Done.\n";

    return 0;
}

        可以混合使用模板参数和常规参数,例如,Crab类的声明可以像下面这样打头:

template<template<typename T> class Thing, typename U, typename V>
class Crab
{    
private:
    Thing<U> s1;
    Thing<V> s2;
    // ...
};

        现在,成员s1和s2可存储的数据类型为泛型,而不是用硬编码指定的类型。

9. 模板类和友元

        模板类声明也可以有友元。模板的友元分为3类:

  • 非模板友元;
  • 约束(bound)模板友元,即友元的类型取决于类被实例化时的类型;
  • 非约束(unbound)模板友元,即友元的所有具体化都是类的每一个具体化的友元。

9.1 模板类的非模板友元函数

        在模板类中将一个常规函数声明为友元:

template<class T>
class HasFriend
{
public:
    friend void counts();
};

        上述声明使counts()函数成为模板所有实例化的友元。例如,它将是类hasFriend<int>和hasFriend<string>的友元。

        counts()函数不是通过对象调用的(它是友元,不是成员函数),也没有对象参数,那么它如何访问HasFriend对象呢?有很多种可能性。它可以访问全局对象;可以使用全局指针访问非全局对象;可以创建自己的对象;可以访问独立于对象的模板类的静态数据成员。

        假设要为友元函数提供模板类参数,可以如下所示来进行友元声明吗?

friend void report(HasFriend&);   //impossible

        答案是不可以,原因是不存在HasFriend这样的对象,而只有特定的具体化,如HasFriend<short>。要提供模板类参数,必须指明具体化。例如:

template<class T>
class HasFriend
{
    friend void report(HasFriend<T>&);    //bound template friend
}

        上述代码示例,带HasFriend<int>参数的report()将成为HasFriend<int>类的友元。同样,带HasFriend<double>参数的report()将是report()的一个重载版本——它是HasFriend<double>类的友元。注意,report()本身并不是模板函数,而只是使用一个模板作参数。这意味着必须为要使用的友元定义显式具体化:

void report(HasFriend<short>&) { };
void report(HasFriend<int>&) { };
//程序清单 - 类模板-8 非模板友元
//frnd2tmp.cpp -- template class with non-template friends

#include <iostream>
using std::cout;
using std::endl;

template<typename T>
class HasFriend
{
private:
    T item;
    static int ct;

public:
    HasFriend(const T& i): item(i) { ct++; }
    ~HasFriend() { ct--; }
    friend void counts();
    friend void reports(HasFriend<T>&);   //模板作参数
};

// each specialization has its own static data member
template<typename T>
int HasFriend<T>::ct = 0;
// non template friend to all HasFriend<T> classes
void counts()
{
    cout << "int count: " << HasFriend<int>::ct << "; ";
    cout << "double count: " << HasFriend<double>::ct << endl;
}
// non template friend to the HasFriend<int> class
void report(HasFriend<int>& hf)
{
    cout << "HasFriend<int>: " << hf.item << endl;
}
// non template friend to the HasFriend<double> class
void reports(HasFriend<double>& hf)
{
    cout << "HasFriend<double>: " << hf.item << endl;
}

int main()
{
    // ...
}

        程序清单类模板-8中,HasFriend模板有一个静态成员ct。这意味着这个类的每一个特定的具体化都将有自己的静态成员。count()方法是所有HasFriend具体化的友元,它报告两个特定的具体化(HasFriend<int>和HasFriend<double>)的ct的值。该程序还提供两个report()函数,它们分别是某个特定HasFriend具体化的友元。

9.2 模板类的约束模板友元函数

        可以修改前一个示例,使友元函数本身成为模板。具体地说,为约束模板友元作准备,要使类的每一个具体化都获得与友元匹配的具体化。这比非模板友元复杂些,包含以下三步:

        首先,在类定义的前面声明每个模板函数。

template <typename T> void counts();
template <typename T> void report(T&);

        然后,在模板类中再次将模板函数声明为友元。这些语句根据类模板参数的类型声明具体化:

template <typename TT>
class HasFriendT
{
    //...
    friend void counts<TT>();
    friend void report<>(HasFriendT<TT>&);
};

        声明中的<>指出这是模板具体化。对于report(),<>可以为空,因为可以从函数参数推断出如下模板类型参数:HasFriend<TT>。也可以使用:

report<HasFriendT<TT> >(HasFriendT<TT>&);

        但counts()函数没有参数,因此必须使用模板参数语法(<TT>)来指明具体化。需要注意,TT是HasFriendT类的参数类型。

        理解这些声明的最佳方式是设想声明一个特定具体化的对象时,它们将变成什么样。例如,假设声明了这样一个对象:

HasFriendT<int> squack;        

        编译器将用int替换TT,并生成下面的类定义:

class HasFriendT<int>
{
//...
    friend void counts<int>();
    friend void report<>(HasFriendT<int>&);
};

        程序清单类模板-9说明了上述知识点,注意类模板-8包含1个counts()函数,它是所有HasFriend类的友元;而类模板-9包含2个counts()函数,它们分别是某个被实例化的类类型的友元。因为counts()函数调用没有可被编译器用来推断出所需具体化的函数参数,所以这些调用使用counts<int>和counts<double>指明具体化。但对于report()调用,编译器可以从参数类型推断出要使用的具体化。使用<>格式也能获得同样的效果:

report<HasFriendT<int> >(hfi2);
// 类模板-9
// tmp2tmp.cpp -- template friends to a template class

#include <iostream>
using std::cout;
using std::endl;

//template prototypes
template<typename T> void counts();
template<typename T> void report(T&);

//template class
template<typename TT>
class HasFriendT
{
private:
    TT item;
    static int ct;
public:
    HasFriendT(const TT& i): item(i) { ct++; }
    ~HasFriendT() { ct--; }
    friend void counts<TT>();
    friend void report<>(HasFriendT<TT>&);
};

template<typename T>
int HasFriendT<T>::ct = 0;

//template friend functions definitions
template<typename T>
void counts()
{
    cout << "template size: " << sizeof(HasFriendT<T>) << "; ";
    cout << "template counts(): " << HasFriendT<T>::ct << endl;
}

template<typename T>
void report(T& hf)
{
    cout << hf.item << endl;
}

int main()
{
    counts<int>();
    HasFriendT<int> hfi1(10);
    HasFriendT<int> hfi2(20);
    HasFriend<double> hfdb(10.5);
    report(hfi1);   //generate report(HasFriendT<int>&)
    report(hfi2);
    report(hfdb);
    cout << "counts<int>() output:\n";
    counts<int>();
    cout << "counts<double>() output:\n";
    counts<double>();

    return 0;
}

9.3 模板类的非约束模板友元函数

        前一节中的约束模板友元函数是在类外面声明的模板的具体化。int类具体化获得int函数具体化,以此类推。通过在类内部声明模板,可以创建非约束友元函数,即每个函数具体化都是每个类具体化的友元。对于非约束友元,友元模板类型参数与模板类类型参数是不同的:

template<typename T>
class ManyFriend
{
    template<typename C, template D> friend void show2(C&, D&);
};

        程序清单类模板-10是一个使用非约束友元的例子。其中,函数调用show2(hfi1, hfi2)与下面的具体化匹配:

void show2<ManyFriend<int>&, ManyFriend<int>& > 
            (ManyFriend<int>& c, ManyFriend<int>& d);

        因为它是所有ManyFriend具体化的友元,所以能够访问所有具体化的item成员,但它只访问了ManyFriend<int>对象。

        同样,show2(hfi1, hfi2)与下面具体化匹配:

void show2<ManyFriend<double>&, ManyFriend<int>& >
            (ManyFriend<double>& c, ManyFriend<int>& d);

        它也是所有ManyFriend具体化的友元,并访问了ManyFriend<int>对象的item成员和ManyFriend<double>对象的item成员。

// 类模板-10
// manyfrnd.cpp -- unbound template friend to a template class

#include <iostream>
using std::cout;
using std::endl;

template<typename T>
class ManyFriend
{
private:
    T item;
public:
    ManyFriend(const T& i): item(i) {}
    template <typename C, typename D> friend void show2(C&, D&);
};

template <typename C, typename D> void show2(C& c, D& d)
{
    cout << c.item << ", " << d.item <<  endl;
}

int main()
{
    ManyFriend<int> hfi1(10);
    ManyFriend<int> hfi2(20);
    ManyFriend<double> hfdb(10.5);
    cout << "hfi1, hfi2: ";
    show2(hfi1, hfi2);
    cout << "hfdb, fhi2: ";
    show2(hfdb, hfi2);

    return 0;
}

10. 模板别名(C++11)

        如果能为类型指定别名,将很方便,在模板设计中尤其如此。可使用typedef为模板具体化指定别名:

// define 3 typedef aliases
typedef std::array<double, 12> arrd;
typedef std::array<int, 12> arri;
typedef std::array<std::string, 12> arrst;
arrd gallons;
arri days;
arrst months;

        C++11新增了一项功能——使用模板提供一系列别名,如下所示:

typename<typename T>
 using arrtype = std::array<T, 12>   // template to create multiple aliases

        这将arrtype定义为一个模板别面,可使用它来指定类型,如下所示:

 arrtype<double> gallons;
 arrtype<int> days;
 arrtype<std::string> months;

        C++11允许将语法using=用于非模板。用于非模板时,这种语法与常规typedef等价:

 typedef const char* pc1;
 using pc2 = const char*;       //using = syntax
 typedef const int *(*pa1)[10];   //typedef syntax
 using pa2 = const int*(*)[10];   //using = syntax

        这种语法的可读性更强,它让类型名和类型信息更清晰。

        C++11新增的另一项模板功能是可变参数模板(variadic template),让您能够定义这样的模板类和模板函数,即可接受可变数量的参数。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++类模板 的相关文章

  • 更新面板工作速度非常慢

    我正在编写一个用户可以注册的应用程序 注册时 可以选择多个选项 并根据这些注册字段可见或不可见以及是否必需 我想出了一个想法 所有字段都将位于 updatePanel 中 当用户更改注册选项时 我将在服务器端设置这些字段的可见性 它可以工作
  • Exit() 时是否调用基本对象析构函数?

    我意识到这个问题已经出现过几次 但我试图获得上述问题的明确答案 但我不断遇到相互矛盾的信息 我需要知道的是 当我使用 exit 时 基本类对象是否被破坏 我知道需要删除动态内存 但我的意思更像是 include
  • 在 HKCR 中创建新密钥有效,但不起作用

    我有以下代码 它返回 成功 但使用两种不同的工具使用搜索字符串 3BDAAC43 E734 11D5 93AF 00105A990292 搜索注册表不会产生任何结果 RegistryKey RK Registry ClassesRoot C
  • 尝试了解使用服务打开对话框

    我已经阅读了有关使用 mvvm 模式打开对话框的讨论 我看过几个使用服务的示例 但我不明白所有部分如何组合在一起 我发布这个问题寻求指导 以了解我应该阅读哪些内容 以更好地理解我所缺少的内容 我将在下面发布我所拥有的内容 它确实有效 但从我
  • Rx.NET 中是否有一个Subject 实现,其功能类似于BehaviourSubject,但仅在值发生更改时才发出?

    有没有Subject https learn microsoft com en us previous versions dotnet reactive extensions hh229699 v vs 103 Rx NET 中的实现在功能
  • 如何在类文件中使用 Url.Action() ?

    如何在 MVC 项目的类文件中使用 Url Action Like namespace 3harf public class myFunction public static void CheckUserAdminPanelPermissi
  • 从复选框列表中选择循环生成的复选框中的一个复选框

    抱歉我的英语不好 在我的 ASP NET 网站上 我从 SQL 表导入软件列表 看起来像这样 但实际上要长得多 Microsoft Application Error Reporting br br Microsoft Applicatio
  • 当事件button.click发生时,如何获取按钮名称/标签?

    我以编程方式制作按钮并将它们添加到堆栈面板中 以便每次用户导航到页面时按钮都会发生变化 我正在尝试做这样的事情 当我单击创建的按钮时 它将获取按钮的标签并转到正确的页面 但是 我无法使用 RoutedEventHandler 访问按钮元素
  • 无法注册时间触发的后台任务

    对于 Windows 8 应用程序 在 C Xaml 中 我尝试注册后台任务 很难说 但我想我的后台任务已正确注册 但是当我单击调试位置工具栏上的后台任务名称时 我的应用程序停止工作 没有任何消息 我查看了事件查看器上的日志 得到 具有入口
  • 强制初始化模板类的静态数据成员

    关于模板类的静态数据成员未初始化存在一些问题 不幸的是 这些都没有能够帮助我解决我的具体问题的答案 我有一个模板类 它有一个静态数据成员 必须为特定类型显式实例化 即必须专门化 如果不是这种情况 使用不同的模板函数应该会导致链接器错误 这是
  • 不同 C++ 文件中的相同类名

    如果两个 C 文件具有相同名称的类的不同定义 那么当它们被编译和链接时 即使没有警告也会抛出一些东西 例如 a cc class Student public std string foo return A void foo a Stude
  • 如何使用 ASP.NET Core 获取其他用户的声明

    我仍在学习 ASP NET Core 的身份 我正在进行基于声明的令牌授权 大多数示例都是关于 当前 登录用户的 就我而言 我的 RPC 服务正在接收身份数据库中某个用户的用户名和密码 我需要 验证是否存在具有此类凭据的用户 获取该用户的所
  • 从 C# 使用 Odbc 调用 Oracle 包函数

    我在 Oracle 包中定义了一个函数 CREATE OR REPLACE PACKAGE BODY TESTUSER TESTPKG as FUNCTION testfunc n IN NUMBER RETURN NUMBER as be
  • Oauth2中如何同时撤销RefreshToken和使AccessToken失效

    我正在使用 Owin Oauth2 授权和资源服务器相同 开发单页面应用程序 AngularJS Net MVC Json Rest API 的身份验证流程 我选择了 Bearer Token 路由而不是传统的 cookie session
  • 模板类中的无效数据类型生成编译时错误?

    我正在使用 C 创建一个字符串类 我希望该类仅接受数据类型 char 和 wchar t 并且我希望编译器在编译时使用 error 捕获任何无效数据类型 我不喜欢使用assert 我怎样才能做到这一点 您可以使用静态断言 促进提供一个 ht
  • 将 Lambda 表达式树与 IEnumerable 结合使用

    我一直在尝试了解有关使用 Lamba 表达式树的更多信息 因此我创建了一个简单的示例 这是代码 如果作为 C 程序粘贴到 LINQPad 中 它可以工作 void Main IEnumerable
  • 代码中的.net Access Forms身份验证“超时”值

    我正在向我的应用程序添加注销过期警报 并希望从我的代码访问我的 web config 表单身份验证 超时 值 我有什么办法可以做到这一点吗 我认为您可以从 FormsAuthentication 静态类方法中读取它 这比直接读取 web c
  • C++:二叉树所有节点值的总和

    我正在准备面试 我被一个二叉树问题困住了 我们如何计算二叉树所有节点中存在的值的总和 优雅的递归解决方案 伪代码 def sum node if node NULL return 0 return node gt value sum nod
  • 如何在 sql azure 上运行 aspnet_regsql? [复制]

    这个问题在这里已经有答案了 可能的重复 将 ASP NET 成员资格数据库迁移到 SQL Azure https stackoverflow com questions 10140774 migrating asp net membersh
  • 无法将字符串文字分配给装箱的 std::string 向量

    这是我的类型系统的简化版本 include

随机推荐

  • React v16.3新生命周期、性能优化及注意事项

    欢迎点击领取 前端面试题进阶指南 前端登顶之巅 最全面的前端知识点梳理总结 React Version 16 3版本对组件的生命周期函数进行了一些修改 在每个react组件中都有以下几个生命周期方法 我们应该掌握最新生命周期 学以致用 以达
  • mciSendString函数简介(播放音乐以及录音相关操作)

    函数功能 播放多媒体音乐 视频等 mciSendString是用来播放多媒体文件的API指令 可以播放MPEG AVI WAV MP3 等等 这个函数有自己的mci指令 可以通过不同的指令实现不同的功能 这里我会详细讲解mciSendStr
  • qemu 启动自定义文件系统命令

    kvm qemu aarch64 bin qemu system aarch64 M virt smp 8 cpu cortex a76 m 4G nographic kernel out kernel arm64 Image append
  • 对以太网粗略理解

    1 以太网定义 以太网 Ethernet 指的是由 Xerox公司创建并由Xerox Intel和 DEC公司联合开发的基带局域网规范 通用的以太网标准于1980年9月30日出台 是当今现有局域网采用的最通用的通信协议标准 是局域网的一种
  • html+css+javascript学习总结

    html用来写页面的结构和内容 css写样式和呈现效果 javascript写行为和动作 1 html常用标签 a 超链接 div 盒子 常用来控制样式的 ul ol 无序列表和有序列表 img 图片标签 button 按钮 form 表单
  • 看懂影片标题,各种电影视频格式标题的含义

    一 资源片源解析 根据命名 可以知道资源的来源 从而判断资源画质的好坏 1 CAM 枪版 珍爱生命 远离枪版 CAM通常是用数码摄像机从电影院盗录 有时会使用小三角架 但大多数时候不可能使用 所以摄像机会抖动 因此我们看到画面通常偏暗 人物
  • 【免费】win7 所有.net framework框架集合,免费下载,若要运行此应用程序,您必须首先安装net framework如何解决

    运行软件缺失框架 若要运行此应用程序 您必须首先安装net framework如何解决 那天我看见网上下载一个框架都要收费还要100大洋 现在真的是干啥都要钱 索性就整理了一个全库供大家下载 做点好事 net 框架所有的 net官网下载地址
  • 摄像机标定到底是在干什么?

    2017年11月13日学习记录 机器视觉 1 摄像机标定概括 刚开始学机器视觉 我研究的方向主要是双目视觉测距 做机器视觉的肯定对摄像机标定并不陌生 我入坑一个月 开始就是看看书 论文 了解了大概流程和研究主要方法 无特别明确目的和压力 然
  • 关于Redis的Zset使用方法

    序言 Zset跟Set之间可以有并集运算 因为他们存储的数据字符串集合 不能有一样的成员出现在一个zset中 但是为什么有了set还要有zset呢 zset叫做有序集合 而set是无序的 zset怎么做到有序的呢 就是zset的每一个成员都
  • Java基础:简单的Runnable 接口创建线程

    创建一个线程 Java 提供了三种创建线程的方法 通过实现 Runnable 接口 通过继承 Thread 类本身 通过 Callable 和 Future 创建线程 为了实现 Runnable 一个类只需要执行一个方法调用 run 声明如
  • 数字图像处理-离散傅里叶变换(opencv3+C++显示)

    参考 http daily zhihu com story 3935067 http blog csdn net keith bb article details 53389819 在学习信号与系统或通信原理等课程里面可能对傅里叶变换有了一
  • 关于局域网、广域网、C/S、P2P编程

    一直以为局域网和广域网的编程没什么不同 实际上确实没什么不同 但这里我要说的是C S和P2P 先说说为局域网编程 局域网编程不用考虑网关 不用考虑NAT 不用考虑消息发送成功后对方将消息丢弃等 这样编程相当简单 只要建立相应的套接口 设置端
  • SQL server基础

    一 SQL Server数据库的数据类型含义 数据类型含义 int 每个数值占用 4字节 2 147 483 648到2 147 483 647之间的整数 smallint 2个字节 存储范围是 32 768 到 32 767 之间的整数
  • Android Studio 红米3 一直运行或者debug不成功,提示 Failed to establish session 解决方案

    换了一个测试机 红米note3开发 一直run OR debug 失败 下面是提示图 找了半天原因 后面发现原因所在了 一般手机默认用开发工具跑起来 会弹出提示 确认是否安装XXX应用 而红米note3就是个奇葩 在它的开发者选项中 有个
  • MATLAB 多目标规划

    作者简介 人工智能专业本科在读 喜欢计算机与编程 写博客记录自己的学习历程 个人主页 小嗷犬的个人主页 个人网站 小嗷犬的技术小站 个人信条 为天地立心 为生民立命 为往圣继绝学 为万世开太平 本文目录 多目标规划 数学模型 正负偏差变量
  • c/c++不定参数函数

    http plutoblog iteye com blog 1150671 不定参数函数 stdarg h是C语言中C标准函数库的头文件 stdarg是由stdandard 标准 arguments 参数 简化而来 主要目的为让函数能够接收
  • WdatePicker日期控件与UEditor富文本编辑器

    WdatePicker日期控件 My97日期控件 下载 更新日志 My97Datepicker Download Changelog 代码中的生日使用插件
  • libevent服务端,多线程应用

    下面的方式是创建多个event base来处理多线程的 主event base用来处理连接请求 各个子event base用来处理读写和关闭请求 另一种方式是 所有的连接 读写 断开操作 都在一个event base里面 然后当读到数据时
  • cesium加webgl的构思

    1 传递gl var gl viewer scene context gl
  • C++类模板

    1 定义类模板 程序清单类模板 1列出了类模板和成员函数模板 明确这些模板不是类和成员函数定义很重要 因为它们是C 编译指令 说明了如何生成类和成员函数定义 不能将模板成员函数放在独立的实现文件中 由于模板不是函数 它们不能单独编译 模板必