C++ template 模板的模板参数(5.4节)

有时,让模板参数本身成为模板是很有用的,我们将继续以stack类模板作为例子,来说明模板的模板参数的用途。在Stack的例子中,如果要使用一个和缺省值不同的内部容器,程序员必须两次指定元素类型。也就是说,为了指定内部容器的类型,你需要同时传递容器的类型和它所含元素的类型。如下:
Stack<int, std::vector<int>> vStack; //使用vector的int栈
然而,借助于模板的模板参数,你可以只指定容器的类型而不需要指定所含元素的类型,就可以声明这个Stack类模板:
Stack<int, std::vector> vStack; //使用vector的int栈
为了获得这个特性,你必须把第2个模板参数指定为模板的模板参数。那么,stack的声明应该如下:
 

//stack7decl.h
#ifndef STACK7DECL_H
#define STACK7DECL_H

template <typename T,
          template <typename ELEM> class CONT = std::deque>
class Stack {
private:
    CONT<T> elems; //
public:
    void push(T const &elem);//添加元素
    void pop();//删除元素
    T top() const;//返回栈顶元素
    bool empty() const {//返回是否为空栈
        return elems.empty();
    }
};

#endif // STACK7DECL_H

不同之处在于,第2个模板参数现在被声明为一个类模板:
template <typename ELEM> class CONT
缺省值也从std::deque<T>变成std::deque。在使用时,第2个参数必须是一个类模板,并且由第一个模板参数传递进来的类型进行实例化:
CONT<T> elems;
这也是这个例子比较特别的地方:使用第1个模板参数作为第2个模板参数的实例化类型。一般地,你可以使用类模板内部的任何类型来实例化模板的模板参数。我们前面提过:作为模板参数的声明,通常可以使用typename来替换关键字class。然而,上面的CONT是为了定义一个类,因此只能使用关键字class。因此,下面的程序是正确的:
template <typename T, template <class ELEM> class CONT = std::deque>  //正确
class Stack {
    ...
};
而下面的程序却是错误的:
template <typename T, template <typename ELEM> typename CONT = std::deque> //错误
class Stack {
    ...
};
由于在这里我们并不会用到“模板的模板参数”的模板参数(即上面的ELEM),所以你可以把该名称省略不写:
template <typename T, template <typename> class CONT = std::deque> //错误
class Stack {
    ...
};

另外,还必须对成员函数的声明进行相应的修改。你必须把第2个模板参数指定为模板的模板参数;这同样适用于成同函数的实现。例如,成员函数push()的实现如下:
template <typename T, template <typename> class CONT>
void Stack<T, CONT>::push(T const &elem)
{
    elems.push_back(elem);    //把elem的拷贝附加到末端
}
还有一点需要知道:函数模板并不支持模板的模板参数。

//stack7.h
#ifndef STACK7_H
#define STACK7_H

#include <deque>
#include <stdexcept>
#include "stack7decl.h"

template <typename T, template <typename> class CONT>
void Stack<T, CONT>::push(T const &elem)
{
    elems.push_back(elem); //添加一个元素
}

template <typename T, template<typename> class CONT>
void Stack<T, CONT>::pop()
{
    if(elems.empty()) {
        throw std::out_of_range("Stack<>::pop(): empty stack");
    }
    elems.pop_back();
}

template <typename T, template <typename> class CONT>
T Stack<T, CONT>::top() const
{
    if(elems.empty()) {
        throw std::out_of_range("Stack<>::top(): empty stack");
    }
    return elems.back();
}

#endif // STACK7_H
//stack7test.cpp
#include <iostream>
#include <string>
#include <cstdlib>
#include <vector>
#include "stack7.h"

int main()
{
    try {
        Stack<int>      intStack;
        Stack<float>    floatStack;

        intStack.push(42);
        intStack.push(5);

        floatStack.push(9.8f);

        std::cout << "floatStack.top()===" << floatStack.top() << std::endl;
        floatStack.pop();
        std::cout << "floatStack.top()===" << floatStack.top() << std::endl;
        floatStack.pop();
        std::cout << "floatStack.top()===" << floatStack.top() << std::endl;
        floatStack.pop();
    } catch (std::exception const &ex) {
        std::cerr << "Exception: " << ex.what() << std::endl;
    }

    Stack<int, std::vector> vStack;

    vStack.push(42);
    vStack.push(7);
    std::cout << "vStack.top()===" << vStack.top() << std::endl;
    vStack.pop();

    return 0;
}


模板的模板实参匹配
如果你尝试使用新版本的Stack,你会获得一个错误信息:缺省值std::deque和模板的模板参数CONT并不匹配。


对于这个结果,你或许会觉得很这诧异,但问题在于:模板的模板实参(譬如这里的std::deque)是一个具有参数A的模板,它将替换模板的模板参数(譬如这里的CONT),而模板的模板参数是一个具有参数B的模板:匹配过程要求参数A和参数B必须完全匹配;然而在这里,我们并没有考虑模板的模板实参数缺省模板参数,从而也就使B缺少了这些缺省参数值 ,当前就不能获得精确的匹配。在这个例子中,问题在于标准库中的std::deque模板还具有另一个参数:即第2个参数(也就是所谓的内存分配器allocator),它有一个缺省值,但在匹配std::deque的参数和CONT的参数时,我们并没有考虑这个缺省值。然而,解决办法总是有的,我们可以重写类的声明,让CONT的参数期待的是具有两个模板参数的容器:
template <typename T, template<typename ELEM, typename ALLOC = std::allocator<ELEM>> class CONT = std::deque>
class Stack {
private:
    CONT<T> elems;
    ...
};
同样,你可以略去ALLOC不写,因为实现中不会用不对劲它。
现在,Stack模板(包括为了能够在不同元素类型的栈之间实现相互赋值而定义的成员模板)的最终版本应该如下:

//stack8.h
#ifndef STACK8_H
#define STACK8_H

#include <deque>
#include <stdexcept>
#include <memory>

template <typename T,
          template <typename ELEM,
                    typename = std::allocator<ELEM>>
                    class CONT = std::deque>
class Stack {
private:
    CONT<T> elems;

public:
    void push(T const &elem);
    void pop();
    T top() const;
    bool empty() const {
        return elems.empty();
    }

    template<typename T2,
             template <typename ELEM2,
                       typename = std::allocator<ELEM2>>
                        class CONT2>
    Stack<T, CONT>& operator= (Stack<T2, CONT2> const &elem);
};

template <typename T, template <typename, typename> class CONT>
void Stack<T, CONT>::push(T const &elem)
{
    elems.push_back(elem);
}

template <typename T, template <typename, typename> class CONT>
void Stack<T, CONT>::pop()
{
    if(elems.empty()) {
        throw std::out_of_range("Stack<>::pop(): empty stack");
    }
    elems.pop_back();
}

template <typename T, template<typename, typename> class CONT>
T Stack<T, CONT>::top() const
{
    if(elems.empty()) {
        throw std::out_of_range("Stack<>::top(): empty stack");
    }
    return elems.back();
}

template <typename T, template<typename, typename> class CONT>
template <typename T2, template<typename, typename> class CONT2>
Stack<T, CONT>&
Stack<T, CONT>::operator= (Stack<T2, CONT2> const &op2)
{
    if((void*)this == (void*)&op2){
        return *this;
    }
    Stack<T2, CONT2> tmp(op2);
    elems.clear();
    while(!tmp.empty()) {
        elems.push_front(tmp.top());
        tmp.pop();
    }
    return *this;
}

#endif // STACK8_H
//stack8test.cpp
#include <iostream>
#include <string>
#include <cstdlib>
#include <vector>
#include "stack8.h"

int main()
{
    try {
        Stack<int> intStack;
        Stack<float> floatStack;

        intStack.push(42);
        intStack.push(9);

        floatStack.push(7.9f);

        floatStack = intStack;

        std::cout << "floatStack.top()===" << floatStack.top() << std::endl;
        floatStack.pop();
        std::cout << "floatStack.top()===" << floatStack.top() << std::endl;
        floatStack.pop();
        std::cout << "floatStack.top()===" << floatStack.top() << std::endl;
        floatStack.pop();
    } catch (std::exception const &ex) {
        std::cerr << "Exception: " << ex.what() << std::endl;
    }

    Stack<int, std::vector> vStack;

    vStack.push(42);
    vStack.push(8);
    std::cout << "vStack.top()=======" << vStack.top() << std::endl;
    vStack.pop();

    return 0;
}

模板的模板参数是要求编译器符合标准的新特性之一;因此,这个程序可以作为评价你的编译器模板特性方面符合标准的尺度。
关于更深入的讨论和这方面的例子,详见8.2.3小节和15.1.6小节。

 

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值