当我们用通用模板去生成类似的的代码,但是针对某一些类型我们需要的代码并没有这么类似,我们这个时候需要手动告诉编译器应该怎么做,编译器针对某些类型去生成对应代码。

  1. #include <iostream>
  2. template <typename T>
  3. bool less_than(const T &a, const T &b) {
  4. return a < b;
  5. }
  6. int main() {
  7. std::clog << less_than(1, 2) << std::endl;
  8. std::clog << less_than(2, 1) << std::endl;
  9. std::clog << less_than(2.1, 1.1) << std::endl;
  10. std::clog << less_than(1.1, 2.2) << std::endl;
  11. std::clog << less_than("zzz", "aaa") << std::endl;
  12. std::clog << less_than("aaa", "zzz") << std::endl;
  13. return 0;
  14. }
  15. // 1
  16. // 0
  17. // 0
  18. // 1
  19. // 0
  20. // 1

例如上面比较同类型的模板函数,在整数和浮点数的情况是正常的。但是在比较字符串时,实际上比较的是两个字符串在内存中的地址,而内存地址是不确定的。

  1. template <size_t M, size_t N>
  2. bool less_than(const char (&a)[M], const char (&b)[N]) {
  3. return strcmp(a, b) < 0;
  4. }

所以我们需要特化一个模板来单独处理字符串类型,但是要考虑到一个函数调用同时满足两个模板参数的情况。

  1. less_than("aaa", "zzz");
  2. candidate function [with T = char [4]]
  3. candidate function [with M = 4, N = 4]

我们声明一个更加特化的版本来比较相同长度的字符串

  1. template <size_t M>
  2. bool less_than(const char (&a)[M], const char (&b)[M]) {
  3. return strcmp(a, b) < 0;
  4. }

偏特化

  1. // 正常
  2. template <typename T, typename U>
  3. class A {
  4. public:
  5. A() {
  6. std::cout << "T,U" << std::endl;
  7. }
  8. };
  9. // 特化
  10. template<>
  11. class A<B, C> {
  12. public:
  13. A() {
  14. std::cout << "B,C" << std::endl;
  15. }
  16. };

如果定义了 A<B, C>()对象,编译器就会使用偏特化版本,而不是模板版本。如果定义了其他泛型对象,编译器就会选择原本的泛型定义。但是有时候你只需要特化某个参数而留下其他泛化参数这个时候就需要偏特化机制。

  1. template<typename T>
  2. class A<T&, B> {
  3. public:
  4. A() {
  5. std::cout << "T, B" << std::endl;
  6. }
  7. };

T 可以接受任何类型,然后在偏特化中用 T 去组合。编译器会把现在的偏特化和全特化的模板做比较并选出最合适的版本。

整数模板参数

模板参数除了类型外(包括基本类型、结构、类类型等),也可以是一个整型数(Integral Number)。这里的整型数比较宽泛,包括布尔、不同位数、有无符号的整型,甚至包括指针。我们将整型的模板参数和类型作为模板参数来做一个对比:

  1. template <typename T> class TemplateWithType;
  2. template <int V> class TemplateWithValue;

我想这个时候你也更能理解 typename 的意思了:它相当于是模板参数的“类型”,告诉你 T 是一个 typename。按照C++ Template最初的想法,模板不就是为了提供一个类型安全、易于调试的宏吗?有类型就够了,为什么要引入整型参数呢?考虑宏,它除了代码替换,还有一个作用是作为常数出现。所以整型模板参数最基本的用途,也是定义一个常数。例如这段代码的作用:

  1. template <typename T, int Size> struct Array
  2. {
  3. T data[Size];
  4. };
  5. Array<int, 16> arr;

编译期计算

  1. template<unsigned N>
  2. struct fibonacci {
  3. static const unsigned value = fibonacci<N - 1>::value + fibonacci<N - 2>::value;
  4. };
  5. template<>
  6. struct fibonacci<1> {
  7. static const unsigned value = 1;
  8. };
  9. template<>
  10. struct fibonacci<0> {
  11. static const unsigned value = 0;
  12. };
  1. // C++11
  2. constexpr unsigned fibonacciFun(unsigned n) {
  3. return n < 2 ? n : fibonacciFun(n - 1) + fibonacciFun(n - 2);
  4. }
  5. // c++ 14 后支持 constexpr 中写 if 分支,并且可以在编译期得知分支
  6. constexpr unsigned fibonacciFun(unsigned n) {
  7. if (n > 2) {
  8. return fibonacciFun(n - 1) + fibonacciFun(n - 2);
  9. } else {
  10. return n;
  11. }
  12. }
  1. int a = 0;
  2. constexpr int *p = &a;
  3. // 如果a是局部变量编译是吧,但是如果a是全局变量则可以通过编译,同理同于字符串
  4. constexpr char *q = "123213"; // 通过
  5. const char a[] = "12323";
  6. constexpr char *q = a; // 不通过