zoukankan      html  css  js  c++  java
  • 第5章 技巧性基础:5.7 模板模板参数

    5.7 Template Template Parameters

    5.7 模板模板参数

    It can be useful to allow a template parameter itself to be a class template. Again, our stack class template can be used as an example.

    允许模板参数本身也是一个类模板,这可能会很有用。我们将继续以stack类模板作为例子。

    To use a different internal container for stacks, the application programmer has to specify the element type twice. Thus, to specify the type of the internal container, you have to pass the type of the container and the type of its elements again:

    在stack的例子中,如果要使用一个和默认值不同的内部容器,程序员必须两次指定元素类型。因此,为了指定内部容器的类型,你需要传入容器的类型及其元素的类型:

    Stack<int, std::vector<int>> vStack; // int型stack ,它使用了一个vector

    Using template template parameters allows you to declare the Stack class template by specifying the type of the container without respecifying the type of its elements:

    然而,借助模板模板参数。声明stack类模板时,只要指定容器的类型,而不需要指定所含元素的类型。

    Stack<int, std::vector> vStack; // 使用vector的int栈

    To do this, you must specify the second template parameter as a template template parameter. In principle, this looks as follows:

    为此,你必须将第2个模板实参指定为模板模板参数。大体上,它看起来如下:

    template<typename T,
             template<typename Elem> class Cont = std::deque>
    class Stack {
    private:
        Cont<T> elems; // elements
    public:
        void push(T const&); // push element
        void pop(); // pop element
        T const& top() const; // return top element
        bool empty() const { // return whether the stack is empty
            return elems.empty();
        }
        ...
    };

    The difference is that the second template parameter is declared as being a class template:

    区别在于,第2个模板参数被声明为类模板:

    template<typename Elem> class Cont;

    The default value has changed from std::deque<T> to std::deque. This parameter has to be a class template, which is instantiated for the type that is passed as the first template parameter:

    默认值已经从std::deque<T>改为std::deque。此参数必须是类模板,并且由第1个模板参数传入进来的类型来实例化。

    Cont<T> elems;

    This use of the first template parameter for the instantiation of the second template parameter is particular to this example. In general, you can instantiate a template template parameter with any type inside a class template.

    将第1个模板参数用于第2个模板参数的实例化,这是本例比较特别的地方。一般地,在类模板中,你可以用任何类型来实例化模板模板参数。

    As usual, instead of typename you could use the keyword class for template parameters. Before C++11, Cont could only be substituted by the name of a class template.

    我们前面提过:模板参数可以用关键字class来替代typename。在C++11之前,上面的Cont只能用类模板的名字来替代(译注:为了表示CONT是一个类类型,早期只能使用关键字class来声明)

    template<typename T, 
             template<class Elem> class Cont = std::deque>
    class Stack { //OK
        …
    };

    Since C++11, we can also substitute Cont with the name of an alias template, but it wasn’t until C++17 that a corresponding change was made to permit the use of the keyword typename instead of class to declare a template template parameter:

    从C++11开始,我们也可以使用别名模板的名称来代替Cont。但直至C++17才做了相应的更改:允许使用关键字typename代替class来声明模板模板参数:

    template<typename T,
             template<typename Elem> typename Cont = std::deque>
    class Stack { //ERROR before C++17
        …
    };

    Those two variants mean exactly the same thing: Using class instead of typename does not prevent us from specifying an alias template as the argument corresponding to the Cont parameter.

    这两处变化的目的都一样:使用class替代typename并不会妨碍我们将别名模板指定为Cont参数对应的参数。

    Because the template parameter of the template template parameter is not used, it is customary to omit its name (unless it provides useful documentation):

    由于在这里我们并不会用到这个“模板模板参数”的模板参数,因此习惯上省略其名称(除非它对编写文档有帮助)

    template<typename T, 
             template<typename> class Cont = std::deque>
    class Stack {
        …
    };

    Member functions must be modified accordingly. Thus, you have to specify the second template parameter as the template template parameter. The same applies to the implementation of the member function. The push() member function, for example, is implemented as follows:

    成员函数必须进行相应的修改。因此,你必须将第2个模板参数指定为模板模板参数。成员函数的实现也是如此。例如push()成员函数的实现如下:

    template<typename T, template<typename> class Cont>
    void Stack<T,Cont>::push (T const& elem)
    {
        elems.push_back(elem); // append copy of passed elem
    }

    Note that while template template parameters are placeholders for class or alias templates, there is no corresponding placeholder for function or variable templates.

    注意,虽然模板模板参数是类或别名模板的占位符,但没有对应的函数或变量模板占位符。(译注:即函数模板或变量模板并不支持模板模板参数?)

    Template Template Argument Matching

    模板模板实参的匹配

    If you try to use the new version of Stack, you may get an error message saying that the default value std::deque is not compatible with the template template parameter Cont. The problem is that prior to C++17 a template template argument had to be a template with parameters that exactly match the parameters of the template template parameter it substitutes, with some exceptions related to variadic template parameters (see Section 12.3.4 on page 197). Default template arguments of template template arguments were not considered, so that a match couldn’t be achieved by leaving out arguments that have default values (in C++17, default arguments are considered).

    如果你尝试使用新版本的stack,你会得到一个错误信息:默认值std::queue和模板模板参数Cont并不匹配。问题在于C++17之前,模板模板实参是一个类模板(如std::queue),其参数必须与其要替代的模板模板参数(如Cont)的模板参数完全匹配。但与可变参数模板相关的有些例外情况(请参阅第197页12.3.4节)。在这里我们并没有考虑模板模板实参的默认值(如std::queue的内存分配器,它有一个默认值),从而无法从这些缺少默认参数值的实参中获得精确的匹配(C++17会考虑这些默认参数值)。

    The pre-C++17 problem in this example is that the std::deque template of the standard library has more than one parameter: The second parameter (which describes an allocator) has a default value, but prior to C++17 this was not considered when matching std::deque to the Cont parameter.

    本例中,C++17之前的问题是标准库中的std::deque模板具有多个参数:第2个参数(用于描述内存分配器)有一个默认值,但C++17之前,在进行std::queue和Cont参数的匹配时,这个默认值并没有被考虑。

    There is a workaround, however. We can rewrite the class declaration so that the Cont parameter expects containers with two template parameters:

    但是,解决办法总是有的。我们可以重写类的声明,让CONT参数期待的是具有两个模板参数的容器:

    template<typename T,
             template<typename Elem, typename Alloc = std::allocator<Elem>> class Cont = std::deque>
    class Stack {
    private:
        Cont<T> elems; // elements
        …
    };

    Again, we could omit Alloc because it is not used.

    同样,我们可以省略Alloc不写,因为实现中不会用到它。

    The final version of our Stack template (including member templates for assignments of stacks of different element types) now looks as follows:

    现在,最终stack类模板(包括为在不同元素类型的栈之间实现相互赋值而定义的成员模板)的最终版本如下:

    #include <deque>
    #include <cassert>
    #include <memory>
    
    template<typename T,
             template<typename Elem, typename = std::allocator<Elem>> class Cont = std::deque>
    class Stack {
    private:
        Cont<T> elems; // elements
    public:
        void push(T const&); // push element
        void pop(); // pop element
        T const& top() const; // return top element
    
        bool empty() const { // return whether the stack is empty
            return elems.empty();
        }
    
        // assign stack of elements of type T2
        template<typename T2, template<typename Elem2, typename = std::allocator<Elem2>>class Cont2>
        Stack<T, Cont>& operator= (Stack<T2, Cont2> const&);
    
        // to get access to private members of any Stack with elements of type T2:
        template<typename, template<typename, typename>class> friend class Stack;
    };
    
    template<typename T, template<typename, typename> class Cont>
    void Stack<T, Cont>::push(T const& elem)
    {
        elems.push_back(elem); // append copy of passed elem
    }
    
    template<typename T, template<typename, typename> class Cont>
    void Stack<T, Cont>::pop()
    {
        assert(!elems.empty());
        elems.pop_back(); // remove last element
    }
    
    template<typename T, template<typename, typename> class Cont>
    T const& Stack<T, Cont>::top() const
    {
        assert(!elems.empty());
        return elems.back(); // return copy of last element
    }
    
    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)
    {
        elems.clear(); // remove existing elements
        elems.insert(elems.begin(), // insert at the beginning
                     op2.elems.begin(), // all elements from op2
                     op2.elems.end());
    
        return *this;
    }

    Note again that to get access to all the members of op2 we declare that all other stack instances are friends (omitting the names of the template parameters):

    再次注意,要访问op2的所有成员,我们将所有其他栈的实例声明为友元(忽略模板参数的名称):

    template<typename, template<typename, typename>class>
    friend class Stack;

    Still, not all standard container templates can be used for Cont parameter. For example, std::array will not work because it includes a nontype template parameter for the array length that has no match in our template template parameter declaration.

    同样,并不是所有标准容器类都可以用做Cont参数。例如std::array就不行,因为它包括了一个表示其长度的非类型模板参数,这与我们的模板模板参数的声明并不匹配。

    The following program uses all features of this final version:

    下面的程序则使用最终版本的所有特性:

    #include "stack9.hpp"
    #include <iostream>
    #include <vector>
    
    int main()
    {
        Stack<int> iStack; // stack of ints
        Stack<float> fStack; // stack of floats
    
        // manipulate int stack
        iStack.push(1);
        iStack.push(2);
        std::cout << "iStack.top(): " << iStack.top() << '
    ';
    
        // manipulate float stack:
        fStack.push(3.3);
        std::cout << "fStack.top(): " << fStack.top() << '
    ';
    
        // assign stack of different type and manipulate again
        fStack = iStack;
        fStack.push(4.4);
        std::cout << "fStack.top(): " << fStack.top() << '
    ';
    
        // stack for doubless using a vector as an internal container
        Stack<double, std::vector> vStack;
        vStack.push(5.5);
        vStack.push(6.6);
        std::cout << "vStack.top(): " << vStack.top() << '
    ';
    
        vStack = fStack;
        std::cout << "vStack: ";
    
        while (!vStack.empty()) {
            std::cout << vStack.top() << ' ';
            vStack.pop();
        }
        std::cout << '
    ';
    }

    The program has the following output:

    程序将会有如下输出:

    iStack.top(): 2
    fStack.top(): 3.3
    fStack.top(): 4.4
    vStack.top(): 6.6
    vStack: 4.4 2 1

    For further discussion and examples of template template parameters, see Section 12.2.3 on page 187, Section 12.3.4 on page 197, and Section 19.2.2 on page 398.

    关于模板模板参数更深入的讨论和这方面的例子,请参阅第187页12.2.3节、第197页12.3.4节以及第398页19.2.2节)。

    5.8 Summary

    5.8 小结

    • To access a type name that depends on a template parameter, you have to qualify the name with a leading typename.

       如果要访问依赖于模板参数的类型名称,你应该在类型名称前面添加关键字typename。

    • To access members of bases classes that depend on template parameters, you have to qualify the access by this-> or their class name.

      为了访问依赖于模板参数的基类成员,你应该使用this->或他们的类名加以限定。

    • Nested classes and member functions can also be templates. One application is the ability to implement generic operations with internal type conversions.

      嵌套类和成员函数也可以是模板。 一种应用是通过内部类型转换实现通用操作的能力。

    • Template versions of constructors or assignment operators don’t replace predefined constructors or assignment operators.

      构造函数和赋值操作符的模板版本并不会取代默认构造函数和赋值操作符。

    • By using braced initialization or explicitly calling a default constructor, you can ensure that variables and members of templates are initialized with a default value even if they are instantiated with a built-in type.

      通过使用大括号初始化或显示调用默认构造函数,你可以确保模板的变量或成员都己经用一个默认值初始化。这种方法对于内置类型的变量和成员也适用。

    • You can provide specific templates for raw arrays, which can also be applicable to string literals.

      你可以为原生数组提供特化模板,这些模板也适用于字符串字面量。

    • When passing raw arrays or string literals, arguments decay (perform an array-topointer conversion) during argument deduction if and only if the parameter is not a reference.

      对于原生数组和或字符串字面量,在实参推导中,当且仅当参数不是引用时,参数才会退化(decay)(转化为数组指针)

    • You can define variable templates (since C++14).

      你可以定义变量模板(从C++14开始)

    • You can also use class templates as template parameters, as template template parameters.

      你也可以使用类模板作为模板参数,也可以用作模板模板参数。

    • Template template arguments must usually match their parameters exactly.

      模板的模板实参经常必须与他们的参数精确地匹配。

  • 相关阅读:
    [BZOJ4444][SCOI2015]国旗计划(倍增)
    [BZOJ4423][AMPPZ2013]Bytehattan(对偶图+并查集)
    [BZOJ4416][SHOI2013]阶乘字符串(子集DP)
    [BZOJ3203][SDOI2013]保护出题人(凸包+三分)
    [BZOJ4026]dC Loves Number Theory(线段树)
    51nod部分容斥题解
    [CodeVS4438]YJQ Runs Upstairs
    [HDU4906]Our happy ending
    牛客网NOIP赛前集训营-提高组(第四场)游记
    [BJWC2011]元素
  • 原文地址:https://www.cnblogs.com/5iedu/p/12755651.html
Copyright © 2011-2022 走看看