美文网首页C++ Templates
【C++ Templates(5)】Tricky Basic

【C++ Templates(5)】Tricky Basic

作者: downdemo | 来源:发表于2018-04-11 15:08 被阅读21次

    关键字typename

    • 默认情况下,C++假定通过作用域运算符访问的名字不是类型,因此要用模板类型参数的类型成员必须加上关键字typename告诉编译器该名字是一个类型,下面是一个typename的典型应用,在模板中访问STL容器的迭代器
    // basics/printcoll.hpp
    #include <iostream>
    
    // print elements of an STL container
    template<typename T>
    void printcoll (T const& coll)
    {
        typename T::const_iterator pos;  // iterator to iterate over coll
        typename T::const_iterator end(coll.end());  // end position
        for (pos=coll.begin(); pos!=end; ++pos) {
            std::cout << *pos << ' ';
        }
        std::cout << '\n';
    }
    
    • 这个模板中调用参数是T类型的容器,为了迭代所有元素必须借助迭代器类型,每个STL容器类都声明有迭代器类型const_iterator
    class stlcontainer {
     public:
        using iterator = ...; // iterator for read/write access
        using const_iterator = ...; // iterator for read access
        ...
    };
    
    • 为了访问模板类型为T的const_iterator类型,需要在声明处加上关键字typename进行限定
    typename T::const_iterator pos;
    

    零初始化

    • 使用模板时,希望模板类型的变量已经用缺省值初始化,但内置类型无法满足要求
    template <typename T>
    void foo()
    {
        T x; // T为内置类型则不会初始化  
    }
    
    • 解决方法是显式调用内置类型的默认构造函数,并把缺省值设置为0(对bool设置为false),比如调用int()获得0
    template <typename T>
    void foo()
    {
        T x{}; // T为内置类型则x为0(或false)
        // C++11前的语法写为
        // T x = T();
    }
    
    • 对于类模板则需要定义一个保证所有成员都初始化的默认构造函数
    template <typename T>
    class A {
    private:
        T x;
    public:
        A() : x() {} // 确保x已被初始化,即使是内置类型
        ...
    };
    
    • C++11中可以为非静态成员提供一个默认初始化
    template <typename T>
    class A {
    private:
        T x{};
        ...
    };
    
    • 但默认实参不能这样写
    template<typename T>
    void foo(T p{}) { // ERROR
        ...
    }
    
    • 正确的写法是
    template<typename T>
    void foo(T p = T{}) { // OK(must use T() before C++11)
        ...
    }
    

    使用this->

    • 对于派生类模板,调用基类的同名函数时,并不一定是使用基类的此函数
    template <typename T>
    class B {
    public:
        void f();
    };
    
    template <typename T>
    class D : B<T> {
    public:
        void f2() { f(); } // 会调用外部的f或者出错
    };
    
    • 这里f2内部调用的f不会考虑基类的f,如果希望调用基类的,使用B<T>::或this->来指定

    原始数组与字符串字面值(string literal)模板

    • 有时把原始数组或string literal传递给函数模板的引用参数会出现问题
    template <typename T>
    T const& max(T const& a, T const& b)
    {
        return a < b ? b : a;
    }
    
    max("apple", "peach"); // OK
    max("apple", "banana"); // 错误:类型不同,分别是char const[6]和char const[7]
    
    • 原因是非引用类型实参在推断过程中会出现数组到指针的转换,比较的实际是指针的地址
    • 能为原始数组和string literal提供特定处理的模板
    // basics/lessarray.hpp
    
    template<typename T,  int N, int M>
    bool less (T(&a)[N], T(&b)[M])
    {
        for (int i = 0; i<N && i<M; ++i) {
            if (a[i]<b[i]) return true;
            if (b[i]<a[i]) return false;
        }
        return N < M;
    }
    
    int x[] = {1, 2, 3};
    int y[] = {1, 2, 3, 4, 5};
    std::cout << less(x,y) << '\n'; // T=int, N=3,M=5
    std::cout << less("ab","abc") << '\n'; // T=char const, N=3,M=4
    
    • 如果只想支持string literal
    // basics/lessstring.hpp
    
    template<int N, int M>
    bool less (char const(&a)[N], char const(&b)[M])
    {
        for (int i = 0; i<N && i<M; ++i) {
            if (a[i]<b[i]) return true;
            if (b[i]<a[i]) return false;
        }
        return N < M;
    }
    
    • 注意对于边界未知的数组,有时必须重载或者局部特化
    // basics/arrays.hpp
    
    #include <iostream>
    
    template<typename T>
    struct MyClass; // primary template
    
    template<typename T, std::size_t SZ>
    struct MyClass<T[SZ]> // partial specialization for arrays of known bounds
    {
        static void print() { std::cout << "print() for T[" << SZ << "]\n"; }
    }; 
    
    template<typename T, std::size_t SZ>
    struct MyClass<T(&)[SZ]> // partial spec. for references to arrays of known bounds
    {
        static void print() { std::cout << "print() for T(&)[" << SZ << "]\n"; }
    }; 
    
    template<typename T>
    struct MyClass<T[]> // partial specialization for arrays of unknown bounds
    {
        static void print() { std::cout << "print() for T[]\n"; }
    }; 
    
    template<typename T>
    struct MyClass<T(&)[]> // partial spec. for references to arrays of unknown bounds
    {
        static void print() { std::cout << "print() for T(&)[]\n"; }
    }; 
    
    template<typename T>
    struct MyClass<T*> // partial specialization for pointers
    {
        static void print() { std::cout << "print() for T*\n"; }
    };
    
    
    // basics/arrays.cpp
    
    #include "arrays.hpp"
    
    template<typename T1, typename T2, typename T3>
    void foo(int a1[7], int a2[],    // pointers by language rules
             int (&a3)[42],          // reference to array of known bound
             int (&x0)[],            // reference to array of unknown bound
             T1 x1,                  // passing by value decays
             T2& x2, T3&& x3)        // passing by reference
    {
        MyClass<decltype(a1)>::print();      // uses MyClass<T*>
        MyClass<decltype(a2)>::print();      // uses MyClass<T*>
        MyClass<decltype(a3)>::print();      // uses MyClass<T(&)[SZ]>
        MyClass<decltype(x0)>::print();      // uses MyClass<T(&)[]>
        MyClass<decltype(x1)>::print();      // uses MyClass<T*>
        MyClass<decltype(x2)>::print();      // uses MyClass<T(&)[]>
        MyClass<decltype(x3)>::print();      // uses MyClass<T(&)[]>
    } 
    
    int main()
    {
        int a[42];
        MyClass<decltype(a)>::print();       // uses MyClass<T[SZ]> 
        extern int x[];                      // forward declare array
        MyClass<decltype(x)>::print();       // uses MyClass<T[]> 
        foo(a, a, a, x, x, x, x);
    } 
    
    int x[] = {0, 8, 15};                // define forward-declared array
    

    成员模板

    • 类成员也可以作为模板,这对嵌套类和成员函数都是可行的。正常情况下不能用不同类型的类互相赋值
    Stack<int> intStack1, intStack2;  // stacks for ints
    Stack<float> floatStack;          // stack for floats
    ...
    intStack1 = intStack2;            // OK: stacks have same type
    floatStack = intStack1;           // ERROR: stacks have different types
    
    • 定义一个赋值运算符模板来实现不同类型的赋值
    // basics/stack5decl.hpp
    
    template<typename T>
    
    class Stack {
    private:
        std::deque<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>
        Stack& operator= (Stack<T2> const&);
    };
    
    • 新的赋值运算符实现如下
    // basics/stack5assign.hpp
    
    template<typename T>
        template<typename T2>
    Stack<T>& Stack<T>::operator= (Stack<T2> const& op2)
    {
        Stack<T2> tmp(op2);              // create a copy of the assigned stack
        elems.clear();                   // remove existing elements
        while (!tmp.empty()) {           // copy all elements
            elems.push_front(tmp.top());
            tmp.pop();
        }
        return *this;
    }
    
    • 为了获取op2所有成员的访问权限,可以把其他的stack实例声明为友元
    // basics/stack6decl.hpp
    
    template<typename T>
    class Stack {
    private:
        std::deque<T> elems;
    public:
        void push(T const&);
        void pop();
    T const& top() const;
        bool empty() const {
            return elems.empty();
        }
    
        // assign stack of elements of type T2
        template<typename T2>
        Stack& operator= (Stack<T2> const&);
    
        // to get access to private members of Stack<T2> for any type T2
        template<typename> friend class Stack;
    };
    
    • 模板赋值运算符的实现
    // basics/stack6assign.hpp
    
    template<typename T>
        template<typename T2>
    Stack<T>& Stack<T>::operator= (Stack<T2> 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;
    }
    
    • 有了这个成员模板,就能把一个int的stack赋值给float的stack
    Stack<int> intStack;      // stack for ints
    Stack<float> floatStack;  // stack for floats
    ...
    floatStack = intStack;    // OK: stacks have different types,
                              //     but int converts to float
    
    • 赋值并没有改变stack和其元素的类型,赋值后floatStack的元素仍为float,top()仍然返回一个float
    • 不用担心这个函数会让类型检查失效而导致可以给一个stack赋值任何类型,必要的类型检查会发生在source stack移到destination stack时
    elems.push_front(tmp.top());
    
    • 如果一个string的stack得到一个float的stack赋值,这一行代码的编译期结果会产生一个错误信息:tmp.top()不能被当作一个实参传递给elems.push_front()
    Stack<std::string> stringStack; // stack of strings
    Stack<float> floatStack; // stack of floats
    ...
    floatStack = stringStack; // ERROR: std::string doesn't convert to float
    
    • 这里可以改变实现来参数化容器的内部类型
    // basics/stack7decl.hpp
    
    template<typename T, typename Cont = std::deque<T>>
    class Stack {
    private:
        Cont 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, typename Cont2>
        Stack& operator= (Stack<T2,Cont2> const&);
    
        // to get access to private members of Stack<T2> for any type T2:
        template<typename, typename> friend class Stack;
    };
    
    • 赋值运算符模板实现如下
    // basics/stack7assign.hpp
    
    template<typename T, typename Cont>
        template<typename T2, typename 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;
    }
    
    • 类模板中只有被调用的成员函数会被实例化,如果禁止不同元素类型的stack赋值,可以使用一个vector作为内部容器,因为赋值运算符模板不是必要的,下面这个程序不会产生缺少push_front()成员函数的错误
    Stack<int,std::vector<int>> vStack;
    ...
    vStack.push(42); vStack.push(7);
    std::cout << vStack.top() << '\n';
    
    • 成员函数模板也能局部或全局特化
    // basics/boolstring.hpp
    
    class BoolString {
    private:
        std::string value;
    public:
        BoolString (std::string const& s)
         : value(s) {
        }
    
        template<typename T = std::string>
        T get() const {         // get value (converted to T)
            return value;
        }
    };
    
    • 成员函数模板的全局特化如下
    // basics/boolstringgetbool.hpp
    
    // full specialization for BoolString::getValue<>() for bool
    template<>
    inline bool BoolString::get<bool>() const {
        return value == "true" || value == "1" || value == "on";
    }
    
    • 类和全局特化的使用如下
    std::cout << std::boolalpha;
    BoolString s1("hello");
    std::cout << s1.get() << '\n';        //prints hello
    std::cout << s1.get<bool>() << '\n';  //prints false
    BoolString s2("on");
    std::cout << s2.get<bool>() << '\n';  //prints true
    
    • 有时调用一个成员模板,显式限定模板实参是有必要的,此时必须使用template关键字来确保<是模板实参列表的开始,考虑下面这个使用标准的bitset类型的例子,如果to_string前没有template,编译器就不知道<是小于号还是模板实参列表的开始
    template<unsigned long N>
    void printBitset (std::bitset<N> const& bs) {
        std::cout << bs.template to_string<char, std::char_traits<char>,
                                         std::allocator<char>>();
    }
    
    • lambda其实是成员模板的简写
    [] (auto x, auto y) {
      return x + y;
    }
    // 等价于下面这个类的一个默认构造对象的简写
    class SomeCompilerSpecificName {
    public:
        SomeCompilerSpecificName();  // constructor only callable by compiler
        template<typename T1, typename T2>
        auto operator() (T1 x, T2 y) const {
          return x + y;
        }
    };
    

    变量模板(Variable Template)

    • C++14中,变量也能被参数化为一个具体类型,称为variable template
    template<typename T>
    constexpr T pi{3.1415926535897932385};
    
    • 对于所有模板,这个声明可能不会出现在函数或block scope内部
    • 使用一个变量模板必须指定类型
    std::cout << pi<double> << '\n';
    std::cout << pi<float> << '\n';
    
    • 可以在不同的编译单元中声明变量模板
    //== header.hpp:
    template<typename T> T val{};     // zero initialized value
    
    //== translation unit 1:
    #include "header.hpp"
    
    int main()
    {
        val<long> = 42;
        print();
    } 
    
    //== translation unit 2:
    #include "header.hpp"
    
    void print()
    {
        std::cout << val<long> << '\n'; // OK: prints 42
    }
    
    • 变量模板也能有默认模板实参
    template<typename T = long double>
    constexpr T pi = T{3.1415926535897932385};
    
    std::cout << pi<> << '\n';       //outputs a long double
    std::cout << pi<float> << '\n';  //outputs a float
    
    • 注意必须有尖括号
    std::cout << pi << '\n';        //ERROR
    
    • 变量模板也能由非类型参数参数化
    #include <iostream>
    #include <array> 
    
    template<int N>
        std::array<int,N> arr{}; // array with N elements, zero-initialized
    template<auto N>
        constexpr decltype(N) dval = N;  // type of dval depends on passed value
    
    int main()
    {
        std::cout << dval<'c'> << '\n'; //N has value 'c' of type char
        arr<10>[0] = 42; // sets first element of global arr
        for (std::size_t i=0; i<arr<10>.size(); ++i) { // uses values set in arr
            std::cout << arr<10>[i] << '\n';
        }
    }
    
    • 变量模板的一个用法是为类模板成员定义变量
    template<typename T>
    class MyClass {
    public:
        static constexpr int max = 1000;
    };
    
    template<typename T>
    int myMax = MyClass<T>::max;
    
    • 这样使用时就可以直接写
    auto i = myMax<std::string>;
    // instead of
    auto i = MyClass<std::string>::max;
    
    • 另一个例子
    namespace std {
        template<typename T> class numeric_limits {
        public:
            ...
            static constexpr bool is_signed = false;
            ...
        };
    }
    
    template<typename T>
    constexpr bool isSigned = std::numeric_limits<T>::is_signed;
    
    isSigned<char>
    // instead of
    std::numeric_limits<char>::is_signed
    
    • C++17开始,标准库使用变量模板来为所有产生一个值的type trait定义简写
    std::is_const_v<T>        // since C++17
    // instead of
    std::is_const<T>::value        //since C++11
    // the standard library defines
    namespace std {
        template<typename T> constexpr bool is_const_v = is_const<T>::value;
    }
    

    模板的模板参数

    • 用模板的模板参数,能做到只指定容器类型而不需要指定元素类型
    Stack<int, std::vector<int>> vStack;
    // 通过模板的模板参数可以写为
    Stack<int, std::vector> vStack;
    
    • 为此必须把第二个模板参数指定为模板的模板参数
    // basics/stack8decl.hpp
    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();
        }
        ...
    };
    
    • 之前Cont只能用class关键字修饰,C++17后可以用typename
    template<typename T,
        template<class Elem> class Cont = std::deque>
    class Stack {                                 //OK
        ...
    };
    
    // Since C++17
    template<typename T,
        template<typename Elem> typename Cont = std::deque>
    class Stack {                                // ERROR before C++17
        ...
    };
    
    • 不过使用时可能会产生错误,原因是容器还有另一个参数,即内存分配器allocator
    template<typename T,
        template<typename Elem,
            typename Alloc = std::allocator<Elem>>
        class Cont = std::deque>
    class Stack {
    private:
        Cont<T> elems;
        ...
    };
    
    • 可以省略Alloc,因为没有用到,最终版本的Stack模板如下
    // basics/stack9.hpp
    
    #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;
    
    public:
        void push(T const&);
        void pop();
        T const& top() const;
        bool empty() const {
            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);
    }
    
    template<typename T, template<typename,typename> class Cont>
    void Stack<T,Cont>::pop ()
    {
        assert(!elems.empty());
        elems.pop_back();
    }
    
    template<typename T, template<typename,typename> class Cont>
    T const& Stack<T,Cont>::top () const
    {
        assert(!elems.empty());
        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)
    {
        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;
    }
    
    • 使用最终版本的所有特性
    // basics/stack9test.cpp
    
    #include  "stack9.hpp"
    #include <iostream>
    #include <vector>
    
    int main()
    {
        Stack<int>   iStack;
        Stack<float> fStack;
    
        iStack.push(1);
        iStack.push(2);
        std::cout << "iStack.top(): " << iStack.top() << '\n';
    
        fStack.push(3.3);
        std::cout << "fStack.top(): " << fStack.top() << '\n';
    
        // assign stack of different type and manipulate again
        fStack = iStack;
        fStack.push(4.4);
        std::cout << "fStack.top(): " << fStack.top() << '\n';
    
        // stack for doubles 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() << '\n';
    
        vStack = fStack;
        std::cout << "vStack: ";
        while (! vStack.empty()) {
            std::cout << vStack.top() << ' ';
            vStack.pop();
        }
        std::cout << '\n';
    }
    
    • 输出如下
    iStack.top(): 2
    fStack.top(): 3.3
    fStack.top(): 4.4
    vStack.top(): 6.6
    vStack: 4.4 2 1
    

    相关文章

      网友评论

        本文标题:【C++ Templates(5)】Tricky Basic

        本文链接:https://www.haomeiwen.com/subject/kxtzhftx.html