template 是 c++ 相当重要的组成部分,堪称 c++语言的一大利器。在大大小小的 c++ 程序中,模板无处不在。c++ templates 作为模板学习的经典书籍,历来被无数 c++学习者所推崇。第二版书籍覆盖了 c++ 11 14 和 17 标准,值得程序猿们精读学习,特此整理学习笔记,将每一部分自认为较为重要的部分逐条陈列,并对少数错误代码进行修改
1.模板实例化时,模板实参必须支持模板中类型对应的所有运算符操作。
- template <typename T>
- T max(const T &a, const T &b) {
- return a > b? a : b;
- }
-
- class NdGreater {
- };
-
- int main() {
- NdGreater n1, n2;
- ::max(n1, n2); // 不支持 > 编译报错
- }
2.模板编译时会进行两阶段检查
a.模板定义时,进行和类型参数无关的检查,如未定义的符号等。
b.模板实例化时,进行类型参数相关的检查。
- template<typename T>
- void foo(T t) {
- undeclared(); // 如果 undeclared()未定义,第一阶段就会报错,因为与模板参数无关
- static_assert(sizeof(T) > 10, "T too small"); //与模板参数有关,只会在第二阶段报错
- }
3.根据两阶段检查,模板在实例化时要看到完整定义,最简单的方法是将实现放在头文件中。
1.函数模板的模板参数可以通过传递的函数参数进行推断。
2.函数推断时会用到参数类型转换,规则如下:
a.如果函数参数是按引用传递的,任何类型转换都不被允许。(此处有疑问,const 转换还是可以的)
b.如果函数参数是按值传递的,可以进行退化(decay)转换:const(指针或者引用只有顶层 const 可以被忽略) 和 volatile 被忽略;引用变为非引用;数组和函数变为对应指针类型。
- template <typename T>
- void RefFunc(const T &a, const T &b){};
-
- template <typename T>
- void NoRefFunc(T a, T b){};
-
- int main() {
- int *const ic = nullptr;
- const int *ci = nullptr;
- int *p = nullptr;
- RefFunc(p, ic); // ok 顶层const可以被忽略 T 为 int *
- RefFunc(p, ci); // error 底层const不可以忽略
- NoRefFunc(p, ci); // error 底层const不可以忽略
-
- int i = 0;
- int &ri = i;
- NoRefFunc(i, ri); // ok ri从int &转换为int
-
- int arr[4];
- NoRefFunc(p, arr); // ok arr 被推断为int *
-
- NoRefFunc(4, 5.0); // error T 可以推断为int或double
- }
3.上文的最后一句调用,类型推断具有二义性,无法正确实例化。可以通过以下方式解决
a.类型转换:
b.显式指定模板实参:
- NoRefFunc(static_cast<double>(4), 5.0); // ok 类型转换
- NoRefFunc<int>(4, 5.0); // 显式指定
4.函数模板无法通过默认参数推断模板参数。如果函数模板只有一个函数参数,且函数参数提供了默认值的情况,应该为模板类型参数 T 也提供和函数参数默认值匹配的默认类型。

资料领取直通车:大厂面试题锦集+视频教程
https://docs.qq.com/doc/DTlhVekRrZUdDUEpy
Linux服务器学习网站:C/C++Linux服务器开发/后台架构师
https://ke.qq.com/course/417774?flowToken=1028592
- template <typename T>
- void Default(T t = 0){};
-
- Default(); // error 无法推断为int
-
- template <typename T = int>
- void Default(T t = 0){};
-
- Default(); // ok 默认类型为int
1.当函数返回类型不能或不便由函数参数类型直接推断时,可以在函数模版中新增模板参赛指定返回类型。
2.c++11 之后,可以通过 auto + decltype +尾后返回类型 推断函数模板返回类型。当函数参数为引用类型时,返回类型应该为非引用。而decltype 会保留引用,因此还需通过 decay 进行类型退化。
3.c++14 之后,可以通过 auto 直接推断函数模板返回类型,前提是函数内部的多个返回语句推断出的返回类型要一致。auto 会自动对类型进行 decay。
4.c++11 之后,可以通过 common_type 返回多个模版类型参赛的公共类型,common_type 返回的类型也是 decay 的。
- #include<type_traits>
- // 单独通过RT指定返回类型
- template <typename RT, typename T1, typename T2>
- RT max1(const T1& a, const T2& b) { return a > b ? a : b; }
-
- // auto c++11支持 通过decay 进行类型退化 typename 用于声明嵌套从属名称 type 为类型而不是成员
- template <typename T1, typename T2>
- auto max2(const T1& a, const T2& b) -> typename std::decay<decltype(a > b ? a : b)>::type { return a > b ? a : b; }
-
- // auto c++14支持
- template <typename T1, typename T2>
- auto max3(const T1& a, const T2& b) { return a > b ? a : b; }
-
- // common_type c++11支持 max4(5, 7.3) max4(7.4, 5) 的返回类型均被推断为double
- template <typename T1, typename T2>
- typename std::common_type<T1, T2>::type max4(const T1& a, const T2& b) { return a > b ? a : b; }
1.可以给模板参数指定默认值。
- // 默认模板参赛 因为RT需要T1 T2推断,所以放在最后
- template <typename T1, typename T2, typename RT = typename std::common_type<T1, T2>::type>
- RT max5(const T1& a, const T2& b) { return a > b ? a : b; }
1.一个非模板函数可以和同名的函数模板共存,并且函数模板可实例化为和非模板函数具有相同类型参数的函数。函数调用时,若匹配度相同,将优先调用非模板函数。但若显式指定模板列表,则优先调用函数模板。
2.函数模板不可以进行类型自动转换,非模板函数可以。
- #pragma once
-
- template <typename T>
- T max(T a, T b) {
- return a > b ? a : b;
- }
-
- template <typename RT, typename T1, typename T2>
- RT max(T1 a, T2 b) {
- return a > b ? a : b;
- }
- int max(int a, int b) {
- return a > b ? a : b;
- }
-
- int main() {
- ::max(6, 8); // 调用非模板函数
- ::max<>(6, 8); // 调用函数模板 max<int>
- ::max('a', 'b'); // 调用函数模板 max<char>
- ::max(4, 4.0); // 通过类型转换调用非模板函数
- ::max<double>(4, 4.0); //指定了返回类型 调用max<double,int,double>
- }
3.调用函数模板时,必须保证函数模板已经定义。
- int max(int a, int b) {
- return a > b ? a : b;
- }
-
- template <typename T>
- T max(T a, T b, T c) {
- return max(max(a,b),c); //T为int时,并不会调用max<int> 而是调用非模板函数
- }
-
- template <typename T>
- T max(T a, T b) {
- return a > b ? a : b;
- }
-
- max(1, 2, 3); // 最终调用非模板函数比较
- max("sjx", "wyl", "shh"); // error 找不到二元的max<const char *>
1.类模板不可以定义在函数作用域或者块作用域内部,通常定义在 global/namespace/类作用域。
- #include<vector>
- #include<iostream>
-
- template <typename T>
- class Stack
- {
- public:
- void push(const T& value);
- void pop();
- T top();
- int size() const { elem_.size(); };
- bool empty() const { return elem_.empty(); };
-
- void print(std::ostream & out) const;
- protected:
- std::vector<T> elem_;
- };
-
- template <typename T>
- void Stack<T>::push(const T &value)
- {
- elem_.push_back(value);
- }
-
- template <typename T>
- void Stack<T>::pop()
- {
- elem_.pop_back();
- }
-
- template <typename T>
- T Stack<T>::top()
- {
- return elem_.back();
- }
-
- template <typename T>
- void Stack<T>::print(std::ostream &out) const
- {
- for (auto e : elem_)
- {
- out << e << std::endl;
- }
- }
1.直到 c++17,使用类模板都需要显式指定模板参数。
2.类模板的成员函数只有在调用的时候才会实例化。
1.类模板实例化时,模板实参只需要支持被实例化部分所有用到的操作。
- int main()
- {
- // 只会实例化类模板中的push 和 print函数
- Stack<int> s;
- s.push(3);
- s.print(std::cout);
-
- // Stack<int>未重载<<运算符,实例化print函数时失败
- Stack<Stack<int>> ss;
- ss.push(s);
- ss.print(std::cout);
- return 0;
- }
2.c++11 开始,可以通过 static_assert 和 type_traits 做一些简单的类型检查
- template <typename T>
- class C
- {
- static_assert(std::is_default_constructible<T>::value, "class C requires default contructible");
- };
1.可以对类模板的一个参数进行特化,类模板特化的同时需要特化所有的成员函数,非特化的函数在特化后的模板中属于未定义函数,无法使用。
- // stringle类型特化
- template <>
- class Stack<std::string>
- {
- public:
- void push(const std::string& value);
- /* 特化其他成员函数*/
- };
1.类模板特化时,可以只特化部分参数,或者对参数进行部分特化。
- // 指针类型特化
- template <typename T>
- class Stack<T *>
- {
- public:
- void push(T *value);
- void pop();
- T* top();
- int size() const { elem_.size(); };
- bool empty() const { return elem_.empty(); };
- protected:
- std::vector<T *> elem_;
- };
-
- template <typename T>
- void Stack<T*>::push(T *value)
- {
- elem_.push_back(value);
- }
-
- template <typename T>
- void Stack<T*>::pop()
- {
- elem_.pop_back();
- }
-
- template <typename T>
- T* Stack
::top() - {
- return elem_.back();
- }
1.类模板也可以指定默认模板参数。
- template <typename T, typename COND = std::vector
> - class Stack
- {
- public:
- void push(const T& value);
- void pop();
- T top();
- int size() const { elem_.size(); };
- bool empty() const { return elem_.empty(); };
- protected:
- COND elem_;
- };
-
- template <typename T, typename COND>
- void Stack
::push(const T &value) - {
- printf("template 1\n");
- elem_.push_back(value);
- }
-
- template <typename T, typename COND>
- void Stack
::pop() - {
- elem_.pop_back();
- }
-
- template <typename T, typename COND>
- T Stack
::top() - {
- return elem_.back();
- }
1.为了便于使用,可以给类模板定义别名。
- typedef Stack<int> IntStack;
- using DoubleStack = Stack<double>;
2.c++11 开始可以定义别名模板,为一组类型取一个方便的名字。
- template <typename T>
- using DequeStack = Stack<T, std::deque<T>>;
3.c++14 开始,标准库使用别名模板技术,为所有返回一个类型的 type_trait 定义了快捷的使用方式。
- // stl库定义
- namespace std
- {
- template <typename T>
- using add_const_t = typename add_const<T>::type;
- }
-
- typename add_const<T>::type; //c++ 11 使用
- std::add_const_t<T>; //c++14使用
1.c++17 开始,如果构造函数能够推断出所有模板参数的类型,那么不需要指定参数类型了。
- template <typename T>
- class Stack
- {
- public:
- Stack() = default;
- Stack(T e): elem_({e}){};
- protected:
- std::vector<T> elem_;
- };
-
- Stack intStack = 0; //通过构造函数推断为int
2.类型推导时,构造函数参数应该按照值传递,而非按引用。引用传递会导致类型推断时无法进行 decay 转化。
- Stack strStack = "sjx";
- //若构造函数参数为值传递,则T为const char *,引用传递时则为const char[4]
3.c++ 17 支持提供推断指引来提供额外的推断规则,推断指引一般紧跟类模板定义之后。
- // 推断指引,传递字符串常量时会被推断为string
- Stack<const char *> -> Stack<std::string>
1.聚合类:没有显式定义或继承来的构造函数,没有非 public 的非静态成员,没有虚函数,没有 virtual,private ,protected 继承。聚合类也可以是模板。
- template <typename T>
- struct ValueWithComment
- {
- T val;
- std::string comment;
- };
-
- ValueWithComment<int> vc;
- vc.val = 42;
- vc.comment = "sjx";
1.模板参数不一定是类型,可以是数值,如可以给 Stack 指定最大容量,避免使用过程元素增删时的内存调整。
- template <typename T, int MAXSIZE>
- class Stack
- {
- public:
- Stack():num_(0){};
- void push(const T& value);
- void pop();
- T top();
- int size() const { return num_; };
- bool empty() const { return num_ == 0; };
- protected:
- T elem_[MAXSIZE];
- int num_;
- };
-
- template <typename T, int MAXSIZE>
- void Stack<T, MAXSIZE>::push(const T &value)
- {
- printf("template 1\n");
- assert(num_ < MAXSIZE);
- elem_[num_++] = value;
- }
-
- template <typename T, int MAXSIZE>
- void Stack<T, MAXSIZE>::pop()
- {
- assert(num_ > 0);
- --num_;
- }
-
- template <typename T, int MAXSIZE>
- T Stack<T, MAXSIZE>::top()
- {
- assert(num_ > 0);
- return elem_[0];
- }
1.函数模板也可以指定非类型模板参数。
- template<typename T, int VAL>
- T addval(const T &num)
- {
- return num + VAL;
- }
-
- int main()
- {
- std::vector<int> nums = {1, 2, 3, 4, 5};
- std::vector<int>nums2(nums.size(),0);
- std::transform(nums.begin(),nums.end(),nums2.begin(),addval<int,5>);
- for(auto num : nums2)
- {
- printf("%d\n",num);
- }
- }
1.非类型模板参数只能是整形常量(包含枚举),指向 objects/functions/members 的指针,objects 或者 functions 的左值引用,或者是 std::nullptr_t(类型是 nullptr),浮点数和类对象不能作为非类型模板参数。
2.当传递对象的指针或者引用作为模板参数时,对象不能是字符串常量,临时变量或者数据成员以及其它子对象。
3.对于非类型模板参数是 const char*的情况,不同 c++版本有不同限制
a. C++11 中,字符串常量对象必须要有外部链接
b. C++14 中,字符串常量对象必须要有外部链接或内部链接
c. C++17 中, 无链接属性也可
4.内部链接:如果一个名称对编译单元(.cpp)来说是局部的,在链接的时候其他的编译单元无法链接到它且不会与其它编译单元(.cpp)中的同样的名称相冲突。例如 static 函数,inline 函数等。
6.非类型模板参数的实参可以是任何编译器表达式,不过如果在表达式中使用了 operator >,就必须将相应表达式放在括号里面,否则>会被作为模板参数列表末尾的>,从而截断了参数列表。
- #include<string>
-
- template < double VAL > // error 浮点数不能作为非类型模板参数
- double process(double v)
- {
- return v * VAL;
- }
-
- template < std::string name > // error class对象不能作为非类型模板参数
- class MyClass {
- };
-
- template < const char * name >
- class Test{
- };
-
- extern const char s01[] = "sjx"; // 外部链接
- const char s02[] = "sjx"; // 内部链接
-
- template<int I, bool B>
- class C{};
- int main()
- {
- Test<"sjx"> t0; // error before c++17
- Test<s01> t1; // ok
- Test<s02> t2; // since c++14
- static const char s03[] = "sjx"; // 无链接
- Test<s03> t3; // since c++17
-
- C<42, sizeof(int)> 4 > c; //error 第一个>被认为模板参数列表已经结束
- C<42, (sizeof(int)> 4) > c; // ok
- }
1.从 C++17 开始,可以不指定非类型模板参数的具体类型(代之以 auto),从而使其可以用于任意有效的非类型模板参数的类型。
- template <typename T, auto MAXSIZE>
- class Stack
- {
- public:
- using size_type = decltype(MAXSIZE); // 根据MAXSIZE推断类型
- public:
- Stack():num_(0){};
- void push(const T& value);
- void pop();
- T top();
- size_type size() const { num_; };
- bool empty() const { num_ == 0; };
- protected:
- T elem_[MAXSIZE];
- size_type num_;
- };
-
- template <typename T, int MAXSIZE>
- void Stack<T, MAXSIZE>::push(const T &value)
- {
- printf("template 1\n");
- assert(num_ < MAXSIZE);
- elem_[num_++] = value;
- }
-
- template <typename T, int MAXSIZE>
- void Stack<T, MAXSIZE>::pop()
- {
- assert(num_ > 0);
- --num_;
- }
-
- template <typename T, int MAXSIZE>
- T Stack<T, MAXSIZE>::top()
- {
- assert(num_ > 0);
- return elem_[0];
- }
-
- int main()
- {
- Stack<int,20u> s1; // size_type为unsigned int
- Stack<double,40> s2; // size_type为int
- }
1.c++11 开始,模板可以接收一组数量可变的参数。
- #include
- void print(){}; // 递归基
- template<typename T, typename ...Types>
- void print(T firstArg, Types ... args)
- {
- std::cout << firstArg << std::endl;
- print(args...);
- }
-
- int main(){
- print(7.5, "hello", 10); // 调用三次模板函数后再调用普通函数
- }
- // 使用模板函数的递归基,最后只剩一个参数时会优先使用本模板
- template<typename T>
- void print(T arg)
- {
- std::cout << arg << std::endl;
- }
3.c++11 提供了sizeof...运算符来统计可变参数包中的参数数目
- template<typename T, typename ...Types>
- void print(T firstArg, Types ... args)
- {
- std::cout << sizeof...(Types) << std::endl;
- std::cout << sizeof...(args) << std::endl;
- std::cout << firstArg << std::endl;
- print(args...);
- }
4.函数模板实例化时会将可能调用的函数都实例化。
- #include <iostream>
- template<typename T, typename ...Types>
- void print(T firstArg, Types ... args)
- {
- std::cout << firstArg << std::endl;
- if(sizeof...(args) > 0)
- print(args...); // error 缺少递归基。即使只在参数包数目>0时调用,args为0个参数时的print函数
- }
- int main(){
- print(7.5, "hello", 10); // 调用三次模板函数后再调用普通函数
- }
1.C++17 提供了一种可以用来计算参数包(可以有初始值)中所有参数运算结果的二元运算符 ...

- template<typename ...T>
- auto sum(T ...s)
- {
- return (... + s); // ((s1+s2)+s3)...
- }
- template<typename ...T>
- void PrintDouble(const T &... args)
- {
- print(args + args...); // 将args翻倍传给print
- }
-
- template<typename ...T>
- void addOne(const T & ...args)
- {
- print(args + 1 ...); // 1和...中间要有空格
- }
- int main(){
- PrintDouble(7.5, std::string("hello"), 10); // 等价调用print(7.5 + 7.5, std::string("hello") + std::string("hello"), 10 + 10);
- addOne(1, 2, 3); // 输出 2 3 4
- }
- template<typename C, typename ...Index>
- void printElems(const C &coll, Index ...idx)
- {
- print(coll[idx]...);
- }
- template<typename C, std::size_t ...Idx> // 参数包为非类型模板参数
- void printIndex(const C &coll)
- {
- print(coll[Idx]...);
- }
- int main(){
- std::vector<std::string> coll = {"wyl", "sjx", "love"};
- printElems(coll, 2, 0, 1); //相当于调用print(coll[2], coll[0], coll[1]);
- printIndex<std::vector<std::string>, 2, 0, 1>(coll); // 等同于以上调用
- }
- template <typename... T>
- class Tuple;
- Tuple<int, char> t; // 可以表示数据成员的类型
-
- template <typename... T>
- class Variant;
- Variant<int, double, bool, float> v; // 可以表示对象可能的类型
5.推断指引也可以是可变参数的。
- namespace std {
- // std::array a{42,43,44} 会被推断为 std::array<int,3> a{42,43,44}
- template<typename T, typename… U> array(T, U…) -> array<enable_if_t<(is_same_v<T, U> && …), T>, (1 + sizeof…(U))>;
- }
1.c++规定模板中通过域作用符访问的嵌套从属名称不是类型名称,为了表明该名称是类型,需要加上 typename 关键字。
- template<typename T>
- class MyClass
- {
- public:
- void foo()
- {
- /* 若无typename SubType会被当做T中的一个static成员或枚举值
- 下方的表达式被理解为SubType 和 ptr的乘积 */
- typename T::SubType *ptr;
- //...
- }
- };
1.c++中对于未定义默认构造函数的类型对象,定义时一般不会进行默认初始化,这时候对象的值将是未定义的。
2.在模板中定义对象时,为了避免产生未定义的行为,可以进行零初始化。
- template<typename T>
- void foo()
- {
- T x = T(); // 对x提供默认值
- }
1.若类模板的基类也是类模板,这时在类模板中不能直接通过名称调用从基类继承的成员,而应该通过 this-> 或 Base::。
- template<typename T>
- class Base
- {
- public:
- void bar(){};
- };
-
- template<typename T>
- class Derived:public Base<T>
- {
- public:
- void foo(){
- bar(); //ERROR 无法访问基类bar成员,若global存在bar函数,则会访问全局bar函数
- this->bar(); // ok
- Base<T>::bar(); // ok
- }
- };
1.当向模板传递裸数组或字符串常量时,如果是引用传递,则类型不会 decay;如果是值传递,则会 decay。
2.也可以通过将数组或字符串长度作为非类型模板参数,定义可以适配不同长度的裸数组或字符串常量的模板。
- template <typename T>
- void foo(T t){};
-
- template <typename T>
- void RefFoo(const T &t){};
-
- 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 main(){
- foo("hello"); // T 为const char *
- RefFoo("hello"); // T 为const char[6]
- less("sjx", "wyl"); // T 为const char, N,M为3
- }
1.不管类是普通类还是类模板,类中的成员函数都可以定义为模板。
2.若是将构造函数或者赋值运算符定义为模板函数,此时定义的模板函数不会取代默认的的构造函数和赋值运算符。下面定义的 operater = 只能用于不同类型的 stack 之间的赋值,若是相同类型,仍然采用默认的赋值运算符。
- #include<deque>
-
- template <typename T, typename COND = std::deque<T> >
- class Stack
- {
- public:
- void push(const T& value);
- void pop();
- T top();
- int size() const { elem_.size(); };
- bool empty() const { return elem_.empty(); };
- //实现不同类型的stack之间的相互赋值
- template <typename T2>
- Stack<T,COND> & operator = (const Stack<T2>& s);
- protected:
- COND elem_;
- };
-
- template <typename T, typename COND>
- template <typename T2>
- Stack<T,COND> & Stack<T,COND>::operator = (const Stack<T2>& s)
- {
- if((void *)this == (void *)&s)
- {
- return *this;
- }
- Stack<T2> tmp(s);
- elem_.clear();
- while(!tmp.empty())
- {
- elem_.push_front(tmp.top());
- tmp.pop();
- }
- return *this;
- }
2.成员函数模板也可以被偏特化或者全特化。
- #include<string>
-
- class BoolString {
- private:
- std::string value;
- public:
- BoolString (std::string const& s): value(s) {}
- template<typename T = std::string>
- T get() const {
- return value;
- }
- };
-
- // 进行全特化, 全特化版本的成员函数相当于普通函数,
- // 放在头文件中会导致重复定义,因此必须加inline
- template<>
- inline bool BoolString::get<bool>() const
- {
- return value == "true" || value == "1";
- }
-
- int main()
- {
- BoolString s1("hello");
- s1.get(); // "hello"
- s1.get<bool>(); // false
- }
3.当通过依赖于模板参数的对象通过.调用函数模板时,需要通过 template 关键字提示<是成员函数模板参数列表的开始,而非<运算符。
- #include<bitset>
- #include<iostream>
-
- template<unsigned int N>
- void printBitSet(const std::bitset<N> &bs)
- {
- // bs依赖于模板参数N 此时为了表明to_string后是模板参数,需要加template
- std::cout << bs.template to_string<char,std::char_traits<char>,std::allocator<char> >() << std::endl;
- }
4.c++14 引入的泛型 lambda 是对成员函数模板的简化。
- [](auto x, auto y)
- {
- return x + y;
- }
-
- // 编译将以上lamba转化为下方类
- class SomeCompilerSpecificName {
- public:
- SomeCompilerSpecificName();
- template<typename T1, typename T2>
- auto operator() (T1 x, T2 y) const {
- return x + y;
- }
- };
1.c++14 开始,可以通过变量模板对变量进行参数化。
2.变量模板的常见应用场景是定义代表类模板成员的变量模板。
3.c++17 开始,标准库用变量模板为其用来产生一个值(布尔型)的类型萃取定义了简化方式。
- #include<iostream>
-
- template <typename T = double>
- constexpr T pi{3.1415926};
-
- std::cout<< pi<> <<std::endl; // <>不可少,输出3.1415926
- std::cout<< pi<int> <<std::endl; // 输出3
-
- template<typename T>
- class MyClass {
- public:
- static constexpr int max = 1000; // 类静态成员
- };
-
- // 定义变量模板表示类静态成员
- template <typename T>
- int myMax = MyClass<T>::max;
-
- // 使用更方便
- auto i = myMax<int>; // 相当于auto i = MyClass<int>::max;
-
- // 萃取简化方式,since c++17
- namespace std {
- template<typename T>
- constexpr bool is_const_v = is_const<T>::value;
- }
1.当非类型模板参数是一个模板时,我们称它为模板模板参数。
2.实例化时,模板模板参数和实参的模板参数必须完全匹配。
- #include <deque>
- #include <vector>
-
- // 错误定义 deque 中的模板参数有两个:类型和默认参数allocator
- // 而模板模板参数Cont的参数只有类型Elem
- 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();
- }
- };
-
- // 正确定义
- 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();
- }
- };
-
- // 使用
- Stack<int> iStack;
- Stack<double,std::vector> dStack;
1.c++11 引入了引用折叠:创建一个引用的引用,引用就会折叠。除了右值引用的右值引用折叠之后还是右值引用之外,其它的引用全部折叠成左值引用。
2.基于引用折叠和 std::forward,可以实现完美转发:将传入将被参数的基本特性(是否 const,左值、右值引用)转发出去。
- #include <utility>
- #include <iostream>
- class X {
- };
- void g (X&) {
- std::cout << "g() for variable\n";
- }
- void g (X const&) {
- std::cout << "g() for constant\n";
- }
- void g (X&&) {
- std::cout << "g() for movable object\n";
- }
-
- template<typename T>
- void f (T&& val) {
- g(std::forward<T>(val));
- }
- int main()
- {
- X v; // create variable
- X const c; // create constant
- f(v); // f() for variable calls f(X&) => calls g(X&)
- f(c); // f() for constant calls f(X const&) => calls g(X const&)
- f(X()); // f() for temporary calls f(X&&) => calls g(X&&)
- f(std::move(v)); // f() for move-enabled variable calls f(X&&)=>calls g(X&&)
- }
1.当类中定义了模板构造函数时:
a.定义的模板构造函数不会屏蔽默认构造函数。
b.优先选用匹配程度高的构造函数
c.匹配程度相同时,优先选用非模板构造函数
- #include <utility>
- #include <string>
- #include <iostream>
- class Person
- {
- private:
- std::string name;
- public:
- // generic constructor for passed initial name:
- template<typename STR>
- explicit Person(STR&& n) : name(std::forward<STR>(n)) {
- std::cout << "TMPL-CONSTR for ’" << name <<std::endl;
- }
- // copy and move constructor:
- Person (Person const& p) : name(p.name) {
- std::cout << "COPY-CONSTR Person ’" << name << "’\n";
- }
- Person (Person&& p) : name(std::move(p.name)) {
- std::cout << "MOVE-CONSTR Person ’" << name << "’\n";
- }
- };
-
- int main()
- {
- Person p1("tmp"); // ok 调用模板函数n 被推断为const char [4]类型,可以赋值给string
- Person p2(p1); // error 模板函数相比Person (Person const& p)构造函数更匹配,n 被推断为Person &类型,最终将Person对象赋值给string
- Person p3(std::move(p1)); // ok 同等匹配程度时,优先调用Person (Person&& p)
- }
1.c++11 提供了辅助模板 std::enable_if,使用规则如下:
a.第一个参数是布尔表达式,第二个参数为类型。若表达式结果为 true,则 type 成员返回类型参数,此时若未提供第二参数,默认返回 void。
b.若表达式结果为 false,根据替换失败并非错误的原则,包含 std::enable_if 的模板将会被忽略。
2.c++14 提供了别名模板技术(见 2.8 节),可以用 std::enable_if_t<>代替 std::enable_if<>::type.
3.若不想在声明中使用 std::enable_if,可以提供一个额外的、有默认值的模板参数。
- #include <type_traits>
- template<typename T>
- typename std::enable_if<(sizeof(T) > 4)>::type
- foo1(){}
-
- // std::enable_if为true时等同下方函数,未提供第二参数默认返回void; false时函数模板被忽略
- // void foo1(){};
- template<typename T>
- typename std::enable_if<(sizeof(T) > 4), bool>::type
- foo2(){}
-
- // std::enable_if为true时等同下方函数,false时函数模板被忽略
- // bool foo2(){};
-
- // 提供额外默认参数进行类型检查
- template<typename T, typename = typename std::enable_if<(sizeof(T) > 4)>::type>
-
- void foo3(){}
-
- // std::enable_if为true时等同下方函数模板,false时函数模板被忽略
- // template<typename T, typename = void>
- // void foo3(){};
1.通过 std::enable_if 和标准库的类型萃取 std::is_convertiable
- class Person
- {
- private:
- std::string name;
- public:
- // 只有STR可以转换为string时才有效
- template<typename STR, typename = typename std::enable_if<std::is_convertible<STR, std::string>::value>::type>
- explicit Person(STR&& n) : name(std::forward<STR>(n)) {
- std::cout << "TMPL-CONSTR for ’" << name <<std::endl;
- }
- // copy and move constructor:
- Person (Person const& p) : name(p.name) {
- std::cout << "COPY-CONSTR Person ’" << name << "’\n";
- }
- Person (Person&& p) : name(std::move(p.name)) {
- std::cout << "MOVE-CONSTR Person ’" << name << "’\n";
- }
- };
1.c++20 提出了 concept 模板可以进行编译期条件检查,大大简化了 enable_if
- template <typename T>
- concept convert_to_string = std::is_convertible<STR, std::string>::value;
-
- class Person
- {
- private:
- std::string name;
- public:
- // 只有STR可以转换为string时才有效
- template<convert_to_string STR>
- explicit Person(STR&& n) : name(std::forward<STR>(n)) {
- std::cout << "TMPL-CONSTR for ’" << name <<std::endl;
- }
- // copy and move constructor:
- Person (Person const& p) : name(p.name) {
- std::cout << "COPY-CONSTR Person ’" << name << "’\n";
- }
- Person (Person&& p) : name(std::move(p.name)) {
- std::cout << "MOVE-CONSTR Person ’" << name << "’\n";
- }
- };
1.当函数参数按值传递时,原则上所有参数都会被拷贝。
2.当传递的参数是纯右值时,编译器会优化,避免拷贝产生;从 c++17 开始,要求此项优化必须执行。
3.按值传递时,参数会发生 decay,如裸数组退化为指针,const、volatile 等限制符会被删除。
- #include <string>
- template <typename T>
- void foo(T t){};
-
- int main(){
- std::string s("Sjx");
- foo(s); // 进行拷贝
- foo(std::string("sjx")); // 避免拷贝
- foo("sjx"); // T decay为const char *
- }
1.当函数参数按照引用传递时,不会被拷贝,并且不会 decay。
2.当函数参数按照引用传递时,一般是定义为 const 引用,此时推断出的类型 T 中无 const。
3.当参数定义为非常量引用时,说明函数内部可以修改传入参数,此时不允许将临时变量传给左值引用。此时若传入参数是 const 的,则类型 T 被推断为 const,此时函数内部的参数修改将报错.
- template <typename T>
- void print(const T &t){};
-
- template <typename T>
- void out(T &t){};
-
- template <typename T>
- void modify(T &t)
- {
- t = T();
- };
- int main(){
- print("sjx"); // arg为const char[3], T 为char[3]
- std::string s("Sjx");
- out(s); // ok
- out(std::string("sjx")); // error 不能将临时变量传给左值引用
- const std::string name = "sjx";
- modify(name); //error T 被推断为 const std::string, 此时modify不能修改传入参数
- }
4.对于给非 const 引用参数传递 const 对象导致编译失败的情形,可以通过 static_assert std::enable_if 或者 concept 等方式进行检查。
- // static_assert 触发编译期错误
- template <typename T>
- void modify(T &t)
- {
- static_assert(!std::is_const<T>::value, "param is const");
- t = T();
- };
-
- // std::enable_if禁用模板
- template <typename T, typename = typename std::enable_if<!std::is_const<T>::value> >
- void modify(T &t)
- {
- t = T();
- };
-
- // concept禁用模板
- template <typename T>
- concept is_not_const = !std::is_const<T>::value;
-
- template <is_not_const T>
- void modify(T &t)
- {
- t = T();
- };
5.当完美转发时,会将函数参数定义为右值引用。此时若在函数内部用 T 定义未初始化的变量,会编译失败。
- template<typename T>
- void passR(T &&t)
- {
- T x;
- }
-
- std::string s("Sjx");
- passR(s); // error T 推断为string &,初始化时必须绑定到对象
1.c++11 开始,若模板参数定义为按值传递时,调用者可以通过 std::cref 或 std::ref 将参数按照引用传递进去。
2.std::cref 或 std::ref创建了一个 std::reference_wrapper<>的对象,该对象引用了原始参数,并被按值传递给了函数模板。std::reference_wrapper<>对象只支持一个操作:向原始对象的隐式类型转换。
- template<typename T>
- void foo(T arg)
- {
- T x;
- }
-
- int main(){
- std::string s = "hello";
- foo(s); // T 为std::string
- foo(std::cref(s)); // error T 为std::reference_wrapper<std::string> 只支持向std::string的类型转换
- }
1.字符串常量或裸数组传递给模板时,如果是按值传递,则会 decay;如果是按照引用传递,则不会 decay。实际应用时,可以根据函数作用加以选择,若要比较大小,一般是按照引用传递;若是比较参数类型是否相同,则可以是按值传递。
1.函数返回值也可以是按值返回或按引用返回。若返回类型为非常量引用,则表示可以修改返回对象引用的对象。
2.模板中即使使用 T 作为返回类型,也不一定能保证是按值返回。
- template<typename T>
- T retR(T &&p)
- {
- return T();
- }
-
- template<typename T>
- T retV(T t)
- {
- return T();
- }
-
- int main()
- {
- int x;
- retR(x); // error T 被推断为int &
- retV<int &>(x); // error T推断为int &
- }
3.为保证按值返回,可以使用 std::remove_reference<>或 std::decay<>。
1.一般通常按值传递,如有特殊需要,可以结合实际按引用传递。
2.定义的函数模板要明确使用范围,不要过分泛化。
1.模板元编程:在编译期通过模板实例化的过程计算程序结果。
- /* 定义用于编译期判断素数的模板 */
- template<unsigned p ,unsigned d>
- struct DoIsPrime
- {
- // 从p%d !=0 开始依次判断 p%(d-1)!=0,p%(d-2)!=0,...
- static constexpr bool value = (p % d != 0) && DoIsPrime<p, d-1>::value;
- };
-
- // 递归基
- template<unsigned p>
- struct DoIsPrime<p ,2>
- {
- static constexpr bool value = (p % 2 != 0);
- };
-
- // 提供给用户调用的模板
- template<unsigned p>
- struct IsPrime
- {
- static constexpr bool value = DoIsPrime<p, p / 2>::value;
- };
-
- // 对于p/2<2的情形进行特化
- template<>
- struct IsPrime<0> {
- static constexpr bool value = false;
- };
-
- template<>
- struct IsPrime<1> {
- static constexpr bool value = false;
- };
-
- template<>
- struct IsPrime<2> {
- static constexpr bool value = false;
- };
-
- template<>
- struct IsPrime<3> {
- static constexpr bool value = false;
- };
-
-
- int main(){
- IsPrime<9>::value; // 编译期通过层层递归实例化最后得到结果为false
- }
1.c++11 提出了 constexpr 关键字可以用于修饰函数返回值,此时该函数为常量表达式函数,编译器可以在编译期完成该函数的计算。
2.c++11 中规定常量表达式函数在使用前必须要知道完整定义,不能仅仅是声明,同时函数内部只能有一条返回语句。
3.c++14 中移除了常量表达式函数只能有一条返回语句的限制。
4.编译器可以在编译期完成该函数计算,但是是否进行还取决于上下文环境及编译器。
- // constexpr函数只能有一个return语句
- constexpr bool doIsPrime(unsigned p, unsigned d)
- {
- return d != 2 ? (p % d != 0) && doIsPrime(p, d - 1) : (p % 2 != 0);
- }
-
- constexpr bool isPrime(unsigned p)
- {
- return p < 4 ? !(p < 2) : doIsPrime(p, p / 2);
- }
- int main(){
- constexpr bool ret = isPrime(9); // constexpr 限定必须在编译期得到结果
- }
1.可以通过模板偏特化在不同的实现方案之间做选择。
- // 根据是否是素数判断是否喜欢
- template <unsigned int SZ, bool = isPrime(SZ)>
- struct IsLove;
-
- // 特化两个版本处理
- template< unsigned int SZ>
- struct IsLove
false> - {
- static constexpr bool isLove = false;
- };
-
- template< unsigned int SZ>
- struct IsLove
true> - {
- static constexpr bool isLove = true;
- };
- int main()
- {
- IsLove<520>::isLove; // 编译期就知道了520不是喜欢
- }
- #include<vector>
- #include<iostream>
-
- // 返回裸数组长度的模板,只有用裸数组替换时才能成功
- template<typename T, unsigned N>
- std::size_t len (T(&)[N])
- {
- return N;
- }
-
- // 只有含有T::size_type的类型才能替换成功
- template<typename T>
- typename T::size_type len (T const& t)
- {
- return t.size();
- }
-
- // ... 表示为可变参数,匹配所有类型, 但匹配程度最差
- std::size_t len(...)
- {
- return 0;
- }
-
- int main()
- {
- int a[10];
- std::cout << len(a) <<std::endl; // 匹配裸数组
- std::cout << len("sjx") <<std::endl; // 匹配裸数组
- std::vector<int> v;
- std::cout << len(v) <<std::endl; // T::size_type
- int *p;
- std::cout << len(p) <<std::endl; // 函数模板均不匹配,最后调用变参函数
- std::allocator<int> x;
-
- /* std::allocator 定义了size_type,所以匹配T::size_type和变参函数,
- 前者匹配程度更高,因此选择该模板。但在实例化时会发现allocator不存在size成员 */
- std::cout << len(x) <<std::endl; //error
- }
- /* decltype中采用逗号表达式,只有若T中不存在size成员,则替换失败。
- 替换成功时,才会将逗号表达式最后一句作为返回类型 */
- template<typename T>
- auto len (T const& t) -> decltype((void)(t.size()) , T::size_type)
- {
- return t.size();
- }
- // 修改4.1中的print函数
- template<typename T, typename… Types>
- void print (T const& firstArg, Types const&… args)
- {
- std::cout << firstArg << std::endl; // print只有一个参数时,只会编译这一部分
- if constexpr(sizeof…(args) > 0) {
- print(args…); // 只有args不为空的时候才会继续递归实例化
- C++17)
- }
- }
- template<typename T>
- void foo(const T& t){};
-
- // 全特化函数
- template<>
- inline void foo<int>(const int& i){};
- // 声明
- class A;
- extern int v;
- void f();
-
- // 定义
- class A{};
- int v = 1;
- void f(){};
a.常规(比如非模板)非 inline 函数和成员函数,以及非 inline 的全局变量和静态数据成员,在整个程序中只能被定义一次.
b. Class 类型(包含 struct 和 union),模板(包含部分特例化,但不能是全特例化),以及 inline 函数和变量,在一个编译单元中只能被定义一次,而且不同编译单元间的定义 应该相同.
1.模板参数:模板定义中模板参数列表中的参数。
a.函数指针
b. 仿函数
c. 存在一个函数指针或者函数引用的转换函数的 class 类型
- #include <iostream>
- #include <vector>
-
- template<typename Iter, typename Callable>
- void foreach (Iter current, Iter end, Callable op)
- {
- while (current != end) { //as long as not reached the end
- op(*current); // call passed operator for current element
- ++current; // and move iterator to next element
- }
- }
-
- // a function to call:
- void func(int i)
- {
- std::cout << "func() called for: " << i << std::endl;
- }
- // a function object type (for objects that can be used as functions):
- class FuncObj {
- public:
- void operator() (int i) const { //Note: const member function
- std::cout << "FuncObj::op() called for: " << i << std::endl;
- }
- };
- int main()
- {
- std::vector<int> primes = {2, 3, 5, 7, 11, 13, 17, 19};
- foreach (primes.begin(), primes.end(), func); // function as callable (decays to pointer)
- foreach(primes.begin(), primes.end(), &func); // function pointer as callable
- foreach(primes.begin(), primes.end(), FuncObj()); // function object as callable
- foreach(primes.begin(), primes.end(), [] (int i) { //lambda as callable
- std::cout << "lambda called for: " << i << std::endl;});
- }
- #include <type_traits>
- template<typename T>
- class C
- {
- // ensure that T is not void (ignoring const or volatile):
- static_assert(!std::is_same_v<std::remove_cv_t<T>,void>,
- "invalid instantiation of class C for void type");
- public:
- template<typename V>
- void f(V&& v) {
- if constexpr(std::is_reference_v<T>) { … // special code if T is a reference type
- }
- if constexpr(std::is_convertible_v<std::decay_t<V>,T>) { … // special code if V is convertible to T
- }
- if constexpr(std::has_virtual_destructor_v<V>) { … // special code if V has virtual destructor
- }
- }
- };
- // 避免在调用运算符?:的时候不得不去调用 T1 和 T2 的(默认)构造函数,这里使用了std::declval
- #include <utility>
- template<typename T1, typename T2,
- typename RT = typename std::decay_t< decltype(true ? std::declval<T1>() : std::declval<T2>())> >
- RT max (T1 a, T2 b)
- {
- return b < a ? a : b;
- }
- template<typename T>
- void foo(T x)
- {
- auto&& val = get(x);
- // perfectly forward the return value of get() to set():
- set(std::forward<decltype(val)>(val));
- }
- template<typename T, T Z = T{}>
- class RefMem {
- private:
- T zero;
- public:
- RefMem() : zero{Z} {}
- };
-
- int null = 0;
-
- int main()
- {
- RefMem<int> rm1, rm2;
- rm1 = rm2; // OK
- RefMem<int&> rm3; // ERROR: 无法用int & 对Z进行默认初始化
- RefMem<int&, 0> rm4; // ERROR: 不能用0 初始化int & 对象zero
- RefMem<int&,null> rm5, rm6;
- rm5 = rm6; // ERROR: 具有非static引用成员的类,默认赋值运算符会被删掉
- }
2.如果需要禁止引用类型进行实例化,可以使用 std::is_reference 进行判断。
- template<typename T>
- class Cont {
- private:
- T* elems;
- public:
- };
-
- struct Node
- {
- std::string value;
- Cont<Node> next; // only possible if Cont accepts incomplete types
- };
- template<typename T> // a namespace scope class template
- class Data {
- public:
- static constexpr bool copyable = true;
- };
-
- template<typename T> // a namespace scope function template
- void log (T x) {
- }
-
- template<typename T> // a namespace scope variable template (since C++14)
- T zero = 0;
-
- template<typename T> // a namespace scope variable template (since C++14)
- bool dataCopyable = Data<T>::copyable;
-
- template<typename T> // a namespace scope alias template
- using DataList = Data<T*>;
-
- class Collection {
- public:
- template<typename T> // an in-class member class template definition
- class Node {
- };
-
- template<typename T> // an in-class (and therefore implicitly inline)
- T* alloc() { // member function template definition
- }
-
- template<typename T> // a member variable template (since C++14)
- static T zero = 0;
-
- template<typename T> // a member alias template
- using NodePtr = Node<T>*;
- };
- template<typename T>
- union AllocChunk {
- T object;
- unsigned char bytes[sizeof(T)];
- };
3.在类模板内,也可以定义和模板参数无关的非模板成员。
4.类内的模板函数不能是虚函数,普通函数可以是虚函数。
- template<int I>
- class CupBoard
- {
- public:
- class Shelf; // ordinary class in class template
- void open(); // ordinary function in class template
- enum Wood{}; // ordinary enumeration type in class template
- static double totalWeight; // ordinary static data member in class template
- virtual void fun(){}; // 普通函数可以是虚函数
- virtual ~CupBoard(){}; // 析构函数也可以是虚函数
- template <typename U>
- virtual void foo(const U &u){}; // error 模板函数不能是虚函数
- };
- int X;
-
- template <typename T>
- class X; // error 模板名称必须独一无二
-
- extern "C++" template<typename T>
- void normal(); // ok
- extern "C" template <typename T>
- void invalid(); // error 模板函数不能有C链接
-
- template<typename T>
- void external(); // 指向另一文件中定义的external函数模板
-
- template<typename T>
- static void internal(); // 并未指向另一文件中定义的internal函数模板,需要在本文件内提供定义
- static void internal();
- namespace {
- template<typename>
- void otherInternal(); // 并未指向另一文件中定义的otherInternal函数模板
- }
-
- namespace {
- template<typename>
- void otherInternal(); // 并未指向另一文件中定义的otherInternal函数模板
- }
2.非类型模板参数可以是以下形式:
a.整型或枚举类型
b.指针类型
c.指向类成员的指针
d.左值引用
e.std::nullptr_t
f.含有 auto 或者 decltype(auto)的类型(since c++17)
4. c++11 开始,可以通过...定义模板参数包,匹配任意类型和数目的参数。
5. 也可以定义指定类型的非模板参数包,匹配指定类型任意数目的参数。
6. 模板可以提供模板参数的默认值,一旦为一个参数提供默认值,其后的参数都必须已经定义默认值。
7. 若一个模板存在多处声明或声明和定义同时存在,那么可以在这些地方定义模板参数的默认值,不能为一个参数重复定义默认值。
- template<template<typename X> class C> // ok
- void f(C<int>* p);
- template<template<typename X> struct C> // error
- void f(C<int>* p);
- template<template<typename X> union C> // error
- void f(C<int>* p);
- template<template<typename X> typename C> // ok since c++17
- void f(C<int>* p);
-
- template<template<typename T, T*> class Buf> // OK
- class Lexer
- {
- static T* storage; // 模板模板参数的参数不能在声明之外使用
- };
-
- template<typename ...Types> // 模板参数包可以匹配任意参数
- class Tuple{};
-
- Tuple<int> t1; // 匹配int
- Tuple<int, char> t2; // 匹配int char
-
- template<typename T, unsigned... Dimensions> // 非类型模板参数包匹配任意数目的unsigned参数
- class MultiArray {};
- MultiArray<double, 3, 3> matrix; // 定义矩阵
-
- template<typename T1 = int, typename T2> // error T2未定义默认值
- class Test;
-
- template<typename T1, typename T2 = double> // ok
- class Quintuple;
- template<typename T1 = int, typename T2> // ok T2默认值前面已经定义
- class Quintuple;
- template<typename T1 = int> // error 不能重复定义默认值
- class Quintuple;
- #include <vector>
- template<typename DstT, typename SrcT>
- DstT implicit_cast (SrcT const& x) // SrcT can be deduced, but DstT cannot
- {
- return x;
- }
-
- implicit_cast<double> (0); // 需要指定返回类型,若DstT和SrcT顺序调换,使用时需要指定两个类型
-
-
- template<typename ... Ts, int N>
- void f(double (&)[N+1], Ts ... ps); // useless declaration because N cannot be specified or deduced
-
- template<typename T, template<typename U> class Cond>
- class MyCond;
-
- MyCond<int,std::vector> c; // error std::vector 为template<class _Tp, class _Alloc>,虽然_Alloc为默认参数,但仍然不匹配
- template<typename T>
- class Tree {
- friend class Factory; // OK even if first declaration of Factory
- friend class MyNode<T>; // error MyNode未找到声明
- };
-
- template<typename T>
- class Stack {
- public:
- // assign stack of elements of type T2
- template<typename T2>
- Stack<T>& operator= (Stack<T2> const&);
- // to get access to private members of Stack<T2> for any type T2: template<typename> friend class Stack;
- };
- template<typename T>
- class Test
- {
- friend T; // 可以将类型模板参数定义为友元
- };
-
- template<typename T1, typename T2>
- void combine(T1, T2);
- class Mixer {
- friend void combine<>(int&, int&); //OK:T1 = int&,T2 = int&
- friend void combine<int, int>(int, int);//OK:T1 = int,T2 = int
- friend void combine<char>(char, int);//OK:T1 = charT2 = int
- friend void combine<char>(char&, int);// ERROR: doesn’t match combine() template
- friend void combine<>(long, long) { } // ERROR: definition not allowed!
- };
1.名称分为受限名称和非受限名称,受限名称前面有显式的出现 ->, ::, . 这三个限定符
4.ADL 查找范围:
(1)对于基本类型(int, char 等), 该集合为空集
(2)对于指针和数组类型,该集合是所引用类型的关联类和关联名字空间
(3)对于枚举类型,名字空间是名字空间是枚举声明所在的名字空间,对于类成员,关联类是枚举所在的类
(4)对于 class(包含联合类型),关联类包括该类本身,他的外围类,直接基类,间接基类。关联名字空间包括每个关联类所在的名字空间。
(5)对于函数类型, 该集合包含所有参数类型和返回类型的关联类和关联名字空间
(6)对于类 X 的成员指针类型,除了包括成员相关的关联名字空间,关联类,该集合还包括与 X 相关的关联名字空间和关联类
- #include <iostream>
- namespace X {
- template<typename T> void f(T);
- }
- namespace N {
- using namespace X;
- enum E { e1 };
- void f(E) {
- std::cout << "N::f(N::E) called\n";
- }
- }
- void f(int)
- {
- std::cout << "::f(int) called\n";
- }
- int main()
- {
- ::f(N::e1); // qualified function name: no ADL
- f(N::e1); // ordinary lookup finds ::f() and ADL finds N::f(),the latter is preferred
- }
- template<typename T>
- class C {
- friend void f();
- friend void f(C
const&) ; - };
- void g (C<int>* p)
- {
- f(); // error 类内声明的友元类外不可见,普通查找无法找到
- f(*p); // ok 虽然不可见,但是可以通过ADL找到
- }
- int X;
- struct X {
- void f() {
- X* p; // OK:X 指代注入类名
- ::X* q; // 错误:名称查找找到变量名,它隐藏 struct 名
- }
- };
- template<typename T> class C {
- using Type = T;
- struct J {
- C* c; // C refers to a current instantiation
- C<Type>* c2; // C<Type> refers to a current instantiation
- I* i; // I refers to an unknown specialization,
- // because I does not enclose J
- J* j; // J refers to a current instantiation
- };
- struct I {
- C* c; // C refers to a current instantiation
- C<Type>* c2; // C<Type> refers to a current instantiation
- I* i; // I refers to a current instantiation
- };
- };
- template<bool B>
- class Invert {
- public:
- static bool const result = !B;
- };
- void g()
- {
- bool test = Invert<1>0>::result; // error 模板参数被提前结束
- bool test = Invert<(1>0)>::result; // ok
- }
a. 名称出现在一个模板中
b. 名称是受限的
c. 名称不是用于基类的派生列表或构造函数的初始化列表中
d. 名称依赖于模板参数
- namespace N {
- class X {
-
- };
- template<int I> void select(X*);
- }
- void g (N::X* xp)
- {
- select<3>(xp); // ERROR 编译无法知道<3>是模板参数,进而无法判断select是函数调用
- }
2.非依赖型基类:无需知道模板名称就可以完全确定类型的基类。
3.非依赖型基类的派生类中查找一个非受限名称时,会先从非依赖型基类中查找,然后才是模板参数列表。
- template<typename X>
- class Base {
- public:
- int basefield;
- using T = int;
- };
-
- class D1: public Base<Base<void>> { // 实际上不是模板
- public:
- void f() { basefield = 3; } // 继承成员普通访问
- };
- template<typename T>
- class D2 : public Base<double> { // 继承自非依赖型基类
- public:
- void f() { basefield = 7; } // 继承成员普通访问
- T strange; // T 是 Base<double>::T 类型,也就是int, 而不是模板参数T!
- };
1.模板被实例化时,编译器需要知道实例化部分的完整定义。
1.模板实例化存在延迟现象,编译器只会实例化需要的部分。如类模板会只实例化用到的部分成员函数,函数模板如果提供了默认参数,也只会在这个参数会用到的时候实例化它。
1.两阶段查找:编译器在模板解析阶段会检测不依赖于模板参数的非依懒型名称,在模板实例化阶段再检查依懒型名称。
2.Points of Instantiation: 编译器会在需要实例化模板的地方插入实例化点(POI)
1.函数模板实例化过程中,编译器会根据实参的类型和模板参数 T 定义的形式,推导出函数的各个参数的类型,如果最后推导的结论矛盾,则推导失败。
- template<typename T>
- T max (T a, T b)
- {
- return b < a ? a : b;
- }
- auto g = max(1, 1.0); // error 根据1推导T为int 根据1.0推导T为double
-
- template<typename T> void f(T);
- template<typename T> void g(T&);
- double arr[20];
- int const seven = 7;
- f(arr); // T 被decay为double*
- g(arr); //T 推断为 double[20]
- f(seven); // T 被decay为int
- g(seven); // T 推断为 int const
1.函数模板被取地址赋予函数指针时,也会产生实参推导。
2.类中定义了类型转换的模板函数时,在类型转换时可以产生实参推导。
- template<typename T>
- void f(T, T);
- void (*pf)(char, char) = &f; // T 被推断为char
-
- class S {
- public:
- template<typename T> operator T&();
- };
-
- void f(int (&)[20]);
- void g(S s)
- {
- f(s); // 类型转换时T被推导为int[20]
- }
1.模板实参如果是初始化列表时,无法直接完成模板参数类型 T 的推导。若函数参数通过 std::initializer_list 定义,则实参类型需要一致。
- template<typename T> void g(T p);
- template<typename T> void f(std::initializer_list<T>);
-
- int main() {
- g({’a’, ’e’, ’i’, ’o’, ’u’, 42}); // ERROR: T deduced to both char and int
- f({1, 2, 3}); // ERROR: cannot deduce T from a braced list
- f({2, 3, 5, 7, 9}); // OK: T is deduced to int
- }
1.引用折叠:只有两个右值引用会被折叠为右值引用,其它情形都是左值引用

- using RCI = int const&;
- RCI volatile&& r = 42; // OK: r has type int const&
- using RRI = int&&;
- RRI const&& rr = 42; // OK: rr has type int&&
1.根据 SFINAE 原理,编译器在用实参推导模板参数失败时,会将该模板忽略。
- template<typename T, unsigned N>
- T* begin(T (&array)[N])
- {
- return array;
- }
- template<typename Container>
- typename Container::iterator begin(Container& c)
- {
- return c.begin();
- }
- int main()
- {
- std::vector<int> v;
- int a[10];
- ::begin(v); // OK: only container begin() matches, because the first deduction fails
- ::begin(a); // OK: only array begin() matches, because the second substitution fails
- }
1.函数模板和普通函数一样,是可以被重载的。
- #include<iostream>
-
- template<typename T>
- int f(T)
- {
- return 1;
- }
- template<typename T>
- int f(T*)
- {
- return 2;
- }
-
- int main()
- {
- std::cout << f<int*>((int*)0) << std::endl; // 更匹配第一个函数calls f<T>(T)
- std::cout << f<int>((int*)0) << std::endl; // 只匹配第二个函数 calls f<T>(T*)
- std::cout << f((int*)0) << std::endl; // 都匹配,但第二个更特殊,优先选用 calls f<T>(T*)
- }
3.函数签名由以下部分构成:
a. 非受限函数名称
b. 名称所属的类作用域
c. 函数的 const volatile 限定符
d. 函数参数的类型
e. 如果是函数模板,还包括返回类型、模板参数和模板实参。
- // 以下模板的实例化函数可以同时存在
- template<typename T1, typename T2>
- void f1(T1, T2);
- template<typename T1, typename T2>
- void f1(T2, T1);
- template<typename T>
- long f2(T);
- template<typename T>
- char f2(T);
- template<typename T>
- std::string f(T)
- {
- return "Template";
- }
- std::string f(int&)
- {
- return "Nontemplate";
- }
- int main()
- {
- int x = 7;
- std::cout << f(x) << std::endl; // 调用非模板函数
- }
- template<typename T>
- class Types {
- public:
- using I = int;
- };
- template<typename T, typename U = typename Types<T>::I>
- class S; // 模板1声明
- template<>
- class S<void> { // 特化后的模板2定义,可以不用默认参数
- public:
- void f();
- };
- template<> class S<char, char>; // 特化后的模板3声明
- template<> class S<char, 0>; // error 特化失败,0不能当类型
- int main()
- {
- S<int>* pi; // OK: 使用模板1且不需定义
- S<int> e1; // ERROR: 使用模板1且需要定义
- S<void>* pv; // OK: 使用模板2且不需定义
- S<void,int> sv; // OK: 使用模板2且不需定义
- S<void,char> e2; // ERROR: 使用模板1且需要定义
- S<char,char> e3; // ERROR: 使用模板3且需要定义
- }
-
- template<>
- class S<char, char> { // 特化后的模板3定义,此处定义对main中的实例化调用是不可见的
- };
- template<typename T,typename U>
- class Invalid {
- };
- Invalid<double, int> x1; // 实例化一个类模板
- template<typename T>
- class Invalid<T,double>; // ok, 部分偏特化可以和实体化实体同时存在
-
- template<>
- class Invalid<double,int>; // ERROR: 全特化后的模板不能和实例化实体同时存在
- template<typename T>
- class S;
- template<> class S<char**> {
- public:
- void print() const;
- };
-
- // 不能出现template<>
- void S<char**>::print() const
- {
- std::cout << "pointer to pointer to char\n";
- }
- template<typename T>
- int f(T) // #1
- {
- return 1;
- }
- template<typename T>
- int f(T*) // #2
- {
- return 2;
- }
- template<> int f(int) // OK: specialization of #1
- {
- return 3;
- }
- template<> int f(int*) // OK: specialization of #2
- {
- return 4;
- }
-
- template<typename T>
- int f(T, T x = 42)
- {
- return x;
- }
- template<> int f(int, int = 35) // 特化时不能指定默认参数
- {
- return 0;
- }
-
- template<typename T>
- int g(T, T x = 42)
- {
- return x;
- }
- template<> int g(int, int y)
- {
- return y/2; // 返回21
- }
- template<typename T> constexpr std::size_t SZ = sizeof(T);
- template<> constexpr std::size_t SZ<void> = 0;
- template<typename T>
- class Outer {
- public:
- static int code;
- void print() const {
- std::cout << "generic";
- }
-
- void test(){};
- };
-
- template<typename T>
- int Outer<T>::code = 6; // 类外定义
-
- // 对于Outer<void>类型,code和print将采用特化版本,其余采用普通版本
- template<>
- int Outer<void>::code = 12; // 特化静态成员
- template<>
- void Outer<void>::print() const // 特化类模板
- {
- std::cout << "Outer
" ; - }
- template<typename T, int I = 3>
- class S; // primary template
- template<typename T>
- class S<int, T>; // ERROR: 参数类型不匹配
- template<typename T = int>
- class S<T, 10>; // ERROR: 不能有默认参数
- template<int I>
- class S<int, I*2>; // ERROR:不能有非类型表达式
- template<typename U, int K>
- class S<U, K>; // ERROR:特化模板和基本模板无区别
- template<typename T> constexpr std::size_t SZ = sizeof(T);
- template<typename T> constexpr std::size_t SZ<T&> = sizeof(void*);


- template<typename T>
- T accum (T const* beg, T const* end)
- {
- T total{}; // 初始化为零值
- while (beg != end) {
- total += *beg;
- ++beg;
- }
- return total;
- }
-
- int main() {
- int num[] = { 1, 2, 3, 4, 5 };
- std::cout << "the average value of the integer values is "
- << accum(num, num+5) / 5; // 输出3
- char name[] = "templates";
- int length = sizeof(name)-1;
- std::cout << "the average value of the characters in \""
- << name << "\" is "
- << accum(name, name+length) / length;
- // 输出-5 因为字符转为对应的八位ASCII编码数字进行累加,累计超过了char表示的最大数字
- }
- template<typename T>
- struct AccumulationTraits;
- template<>
- struct AccumulationTraits<char> {
- using AccT = int;
- };
- template<>
- struct AccumulationTraits<short> {
- using AccT = int;
- };
- template<>
- struct AccumulationTraits<int> {
- using AccT = long;
- };
- template<>
- struct AccumulationTraits<unsigned int> {
- using AccT = unsigned long;
- };
- template<>
- struct AccumulationTraits<float> {
- using AccT = double;
- };
-
- template<typename T>
- auto accum (T const* beg, T const* end)
- {
- using AccT = typename AccumulationTraits<T>::AccT;
- AccT total{}; // assume this actually creates a zero value
- while (beg != end) {
- total += *beg;
- ++beg;
- }
- return total;
- }
- template<>
- struct AccumulationTraits<char> {
- using AccT = int;
- static AccT const zero = 0;
- };
- template<>
- struct AccumulationTraits<short> {
- using AccT = int;
- static AccT const zero = 0;
- };
- template<>
- struct AccumulationTraits<int> {
- using AccT = long;
- static AccT const zero = 0;
- };
- template<typename T>
- auto accum (T const* beg, T const* end)
- {
- using AccT = typename AccumulationTraits<T>::AccT;
- AccT total = AccumulationTraits<T>::zero;
- while (beg != end) {
- total += *beg;
- ++beg;
- }
- return total;
- }
- template<>
- struct AccumulationTraits<char> {
- using AccT = int;
- static constexpr AccT zero() { // 通过内联函数定义初始值
- return 0;
- }
- };
- template<typename T, typename AT = AccumulationTraits<T>>
- auto accum (T const* beg, T const* end)
- {
- typename AT::AccT total = AT::zero();
- while (beg != end) {
- total += *beg;
- ++beg; }
- return total;
- }
- // 求和策略
- class SumPolicy {
- public:
- template<typename T1, typename T2>
- static void accumulate (T1& total, T2 const& value) {
- total += value;
- }
- };
-
- // 求乘积策略
- class MultPolicy {
- public:
- template<typename T1, typename T2>
- static void accumulate (T1& total, T2 const& value) {
- total *= value;
- }
- };
-
- template<typename T,
- typename Policy = SumPolicy,
- typename Traits = AccumulationTraits<T>>
- auto accum (T const* beg, T const* end)
- {
- using AccT = typename Traits::AccT;
- AccT total = Traits::zero();
- while (beg != end) {
- Policy::accumulate(total, *beg);
- ++beg; }
- return total;
- }
- #include<vector>
- #include<list>
- #include<iostream>
- #include<typeinfo>
- template<typename T>
- struct ElementT; // primary template
- template<typename T>
- struct ElementT<std::vector<T>> { // partial specialization for std::vector
- using Type = T;
- };
- template<typename T>
- struct ElementT<std::list<T>> {
- using Type = T;
- };
-
- // partial specialization for std::list
- template<typename T, std::size_t N>
- struct ElementT<T[N]> {
- using Type = T;
- };
- template<typename T>
- struct ElementT<T[]> {
- using Type = T;
- };
-
- // 类型函数,打印容器类型T中元素的类型
- template<typename T>
- void printElementType (T const& c)
- {
- std::cout << "Container of "
- << typeid(typename ElementT<T>::Type).name()
- << " elements.\n";
- }
-
- int main() {
- std::vector<bool> s;
- printElementType(s); // print b
- int arr[42];
- printElementType(arr); // print i
- }
- // 移除引用
- template<typename T>
- struct RemoveReferenceT {
- using Type = T;
- };
- template<typename T>
- struct RemoveReferenceT<T&> {
- using Type = T;
- };
- template<typename T>
- struct RemoveReferenceT<T&&> {
- using Type = T;
- };
-
- // 添加引用
- template<typename T>
- struct AddLValueReferenceT {
- using Type = T&;
- };
-
- template<typename T>
- struct AddRValueReferenceT {
- using Type = T&&;
- };
- // 判断两个类型是否相同
- template<typename T1, typename T2>
- struct IsSameT {
- static constexpr bool value = false;
- };
- template<typename T>
- struct IsSameT
{ - static constexpr bool value = true;
- };
- #include <iostream>
-
- // 定义bool模板类型
- template<bool val>
- struct BoolConstant {
- using Type = BoolConstant<val>;
- static constexpr bool value = val;
- };
-
- // 定义两个实例化类型表示true 和 false
- using TrueType = BoolConstant<true>;
- using FalseType = BoolConstant<false>;
-
- // 普通IsSameT模板继承FalseType
- template<typename T1, typename T2>
- struct IsSameT : FalseType
- {
- };
-
- // 特化的相同类型IsSameT模板继承TrueType
- template<typename T>
- struct IsSameT<T, T> : TrueType
- {
- };
-
- // 根据fool的结果调用不同的foolImpl
- template<typename T>
- void foolImpl(T, TrueType)
- {
- std::cout<<"true"<<std::endl;
- }
-
- template<typename T>
- void foolImpl(T, FalseType)
- {
- std::cout<<"false"<<std::endl;
- }
-
- template<typename T>
- void fool(T t)
- {
- foolImpl(t, IsSameT<T,int>{}); // IsSameT<T,int>{} 类型可能为TrueType或FalseType
- }
-
- int main()
- {
- fool(5); // print true
- fool(5.0); // print false
- }
- // 实现一个能判断T是否具有默认构造函数的模板
- template<typename T>
- struct IsDefaultConstructibleT {
- private:
- // 定义两个重载的函数模板,返回类型不同,用于传入IsSameT进行判断
- template<typename U, typename = decltype(U())> // decltype(U()) 中U若不可以默认初始化,则替换失败,这个函数模板被淘汰,注意此处不能为T,否则会编译失败
- static char test(void*);
- template<typename>
- static long test(...); // ...参数可以匹配任意实参
- public:
- // 将T传入test,如可以默认构造,则匹配第一个test,test返回类型为char,IsSameT 结果为true,否则为false
- static constexpr bool value = IsSameT<decltype(test<T>(nullptr)), char>::value;
- };
-
- IsDefaultConstructibleT<int>::value //结果为 true
-
- struct S {
- S() = delete;
- };
- IsDefaultConstructibleT<S>::value // 结果为false
- // helper to ignore any number of template parameters:
- template<typename...> using VoidT = void;
- // primary template:
- template<typename, typename = VoidT<>>
- struct IsDefaultConstructibleT : std::false_type
- {
- };
- // partial specialization (may be SFINAE’d away):
- template<typename T>
- struct IsDefaultConstructibleT<T, VoidT<decltype(T())>> : std::true_type
- {
- };
1.可以基于 SFINAE 原理探测类型 T 中是否含有名为 X 的成员。
- // helper to ignore any number of template parameters:
- template<typename...> using VoidT = void;
- // primary template:
- template<typename, typename = VoidT<>>
- struct HasSizeTypeT : std::false_type
- {
- };
- // 只有含有size_type时才会替换成功,其余情况偏特化的模板被忽略
- template<typename T>
- struct HasSizeTypeT<T, VoidT<typename T::size_type>> : std::true_type
- {
- };
-
- std::cout << HasSizeTypeT<int>::value; // false
- struct CX {
- using size_type = std::size_t;
- };
- std::cout << HasSizeType<CX>::value; // true
2.对于 13.2 中的介绍的注入的类名词,上述检查结果为 true。
- struct size_type { };
- struct Sizeable : size_type { };
- std::cout << HasSizeTypeT<Sizeable>::value; //ture
- #define DEFINE_HAS_TYPE(MemType) \
- template<typename, typename = std::void_t<>> \
- struct HasTypeT_##MemType \
- : std::false_type { \
- }; \
- template<typename T> \
- struct HasTypeT_##MemType<T, std::void_t<typename T::MemType>> \ : std::true_type { }
- // 定义两种类型萃取
- DEFINE_HAS_TYPE(value_type);
- DEFINE_HAS_TYPE(size_type);
- #define DEFINE_HAS_MEMBER(Member) \
- template<typename, typename = std::void_t<>> \
- struct HasMemberT_##Member \ : std::false_type { }; \
- template<typename T> \
- struct HasMemberT_##Member<T, std::void_t<decltype(&T::Member)>> \
- : std::true_type { }
1.if-then-else:接收一个条件参数,根据条件从两个类型参数中做选择。
- template<bool COND, typename TrueType, typename FalseType>
- struct IfThenElseT {
- using Type = TrueType;
- };
- // 模板偏特化
- template<typename TrueType, typename FalseType> struct IfThenElseT<false, TrueType, FalseType> {
- using Type = FalseType;
- };
- template<bool COND, typename TrueType, typename FalseType>
- using IfThenElse = typename IfThenElseT<COND, TrueType, FalseType>::Type;
- template<typename T> void swap(T& x, T& y)
- {
- T tmp(x);
- x = y;
- y = tmp;
- }
-
- // Array<T> 参数将优先匹配下面的版本
- template<typename T>
- void swap(Array<T>& x, Array<T>& y) {
- swap(x.ptr, y.ptr);
- swap(x.len, y.len);
- }
- class Empty {
- using Int = int;// type alias members don’t make a class nonempty
- };
- class EmptyToo : public Empty { };
- class EmptyThree : public EmptyToo { };
- int main()
- {
- // 输出的三个类大小相同
- std::cout << "sizeof(Empty): " << sizeof(Empty) << ’\n’;
- std::cout << "sizeof(EmptyToo): " << sizeof(EmptyToo) << ’\n’;
- std::cout << "sizeof(EmptyThree): " << sizeof(EmptyThree) << ’\n’;
- }
- // 两种crtp形式
- template<typename Derived> class CuriousBase {
- };
- class Curious : public CuriousBase<Curious> {
- };
-
- template<typename Derived> class CuriousBase {
- };
- template<typename T>
- class CuriousTemplate : public CuriousBase<CuriousTemplate<T>> {
- };
1.值元编程:在编译期通过模板基于输入参数计算得到结果值。
- // 计算平方根的模板
- template<typename T>
- constexpr T sqrt(T x)
- {
- if (x <= 1) {
- return x;
- }
- T lo = 0, hi = x;
- for (;;)
- {
- auto mid = (hi+lo)/2, midSquared = mid*mid;
- if (lo+1 >= hi || midSquared == x) {
- return mid;
- }
- if (midSquared < x) { lo = mid;
- } else {
- hi = mid;
- }
- }
- }
-
- sqrt(9); // 编译期计算结果
2.类型元编程:在编译期通过模板基于输入类型得到输出类型。
- // primary template: in general we yield the given type:
- template<typename T> struct RemoveAllExtentsT {
- using Type = T;
- };
- // partial specializations for array types (with and without bounds):
- template<typename T, std::size_t SZ>
- struct RemoveAllExtentsT<T[SZ]> {
- using Type = typename RemoveAllExtentsT<T>::Type;
- };
- template<typename T>
- struct RemoveAllExtentsT<T[]> {
- using Type = typename RemoveAllExtentsT<T>::Type;
- };
- template<typename T>
- using RemoveAllExtents = typename RemoveAllExtentsT<T>::Type;
- // 计算向量的点乘
- template<typename T, std::size_t N>
- struct DotProductT {
- static inline T result(T* a, T* b)
- {
- return *a * *b + DotProduct<T, N-1>::result(a+1,b+1);
- }
- };
-
- template<typename T>
- struct DotProductT<T, 0> {
- static inline T result(T*, T*) {
- return T{};
- }
- };
-
- template<typename T, std::size_t N>
- auto dotProduct(std::array<T, N> const& x, std::array<T, N> const& y)
- {
- return DotProductT<T, N>::result(x.begin(), y.begin());
- }