模板的定义:模板就是实现代码重用机制的一种工具,它可以实现类型参数化,即把类型定义为参数, 从而实现了真正的代码可重用性。
模板分为函数模板和类模板,如下所示

  1. #include <iostream>
  2. using namespace std;
  3. //模板函数
  4. template<typename T>
  5. void add(T num1, T num2) {
  6. cout << num1 << " + " << num2 << " = "<< num1 + num2 << endl;
  7. }
  8. //模板类
  9. template<typename T>
  10. class Test_Class {
  11. public:
  12. static void multi(T num1, T num2) {
  13. cout << num1 << " * " << num2 << " = "<< num1 * num2 << endl;
  14. }
  15. };
  16. int main(){
  17. //Test 1
  18. int num1 = 1;
  19. int num2 = 2;
  20. add<int>(num1, num2);
  21. Test_Class<int>::multi(num1, num2);
  22. //Test 2
  23. double num3 = 3.1;
  24. double num4 = 4.2;
  25. add<double>(num3, num4);
  26. Test_Class<double>::multi(num3, num4);
  27. return 0;
  28. }

函数模版

函数模板是可以被重载的(类模板不能被重载),也就是说允许存在两个同名的函数模板,还可以对它们进行实例化,使它们具有相同的参数类型。

  1. #include <iostream>
  2. using namespace std;
  3. //函数模板
  4. template <typename T>
  5. int fun(T){
  6. return 1;
  7. }
  8. //函数模板的重载
  9. template <typename T>
  10. int fun(T*){
  11. return 2;
  12. }
  13. int main(){
  14. cout << fun<int*>((int*)0) << endl;
  15. cout << fun<int>((int*)0) << endl;
  16. return 0;
  17. }

函数模版特化

模板特化的原因:模板并非对任何模板实参都合适、都能实例化,某些情况下,通用模板的定义对特定类型不合适,可能会编译失败,或者得不到正确的结果。因此,当不希望使用模板版本时,可以定义类或者函数模板的一个特例化版本。

模板特化:模板参数在某种特定类型下的具体实现。分为函数模板特化和类模板特化

函数模板特化:将函数模板中的全部类型进行特例化,称为函数模板特化。
类模板特化:将类模板中的部分或全部类型进行特例化,称为类模板特化。
特化分为全特化和偏特化:

全特化:模板中的模板参数全部特例化。
偏特化:模板中的模板参数只确定了一部分,剩余部分需要在编译器编译时确定。
说明:要区分下函数重载与函数模板特化
定义函数模板的特化版本,本质上是接管了编译器的工作,为原函数模板定义了一个特殊实例,而不是函数重载,函数模板特化并不影响函数匹配。

#include <iostream>
#include <cstring>

using namespace std;
//函数模板
template <class T>
bool compare(T t1, T t2)
{
    cout << "通用版本:";
    return t1 == t2;
}

template <> //函数模板特化
bool compare(char *t1, char *t2)
{
    cout << "特化版本:";
    return strcmp(t1, t2) == 0;
}

int main(int argc, char *argv[])
{
    char arr1[] = "hello";
    char arr2[] = "abc";
    cout << compare(123, 123) << endl;
    cout << compare(arr1, arr2) << endl;

    return 0;
}


类模版

C++ Template 不仅用在function上,同时也用在class上面

facelift中的例子

namespace facelift {

template<typename ElementType>
class ModelProperty : public Model<ElementType>, public PropertyBase
{
public:
    typedef std::function<ElementType(int)> ElementGetter;

    ModelProperty()
    {
    }

    template<typename Class>
    ModelProperty &bind(const ModelPropertyInterface<Class, ElementType> &property)
    {
        facelift::Model<ElementType>* model = property.property();
        this->bindOtherModel(model);

        this->beginResetModel();
        this->reset(property.property()->size(), [model](int index) {
            return model->elementAt(index);
        });
        this->endResetModel();
        return *this;
    }
    ...
 }

C++中为什么用模版类

1、可用来创建动态增长和减小的数据结构
2、它是类型无关的,因此具有很高的可复用性。
3、它在编译时而不是运行时检查数据类型,保证了类型安全
4、它是平台无关的,可移植性
5、可用于基本数据类型

Template specialization (特化)

If we want to define a different implementation for a template when a specific type is passed as template parameter, we can declare a specialization of that template.

For example, let’s suppose that we have a very simple class called mycontainer that can store one element of any type and that it has just one member function called increase, which increases its value. But we find that when it stores an element of type char it would be more convenient to have a completely different implementation with a function member uppercase, so we decide to declare a class template specialization for that type:

对于特定类型的特定操作,我们可以通过进行特定实现来使其针对这种template的类型拥有更多的功能,比如对于下面这个例子,对于char类型,通过template specialization的方式,令其拥有了uppercase的方法。

// template specialization
#include <iostream>
using namespace std;

// class template:
template <class T>
class mycontainer {
    T element;
  public:
    mycontainer (T arg) {element=arg;}
    T increase () {return ++element;}
};

// class template specialization:
template <>
class mycontainer <char> {
    char element;
  public:
    mycontainer (char arg) {element=arg;}
    char uppercase ()
    {
      if ((element>='a')&&(element<='z'))
      element+='A'-'a';
      return element;
    }
};

int main () {
  mycontainer<int> myint (7);
  mycontainer<char> mychar ('j');
  cout << myint.increase() << endl;
  cout << mychar.uppercase() << endl;
  return 0;
}

特化也分为了两种,全特化和偏特化。
不能将特化和重载混为一谈
全特化和偏特化都没有引入一个全新的模板或者模板实例。它们只是对原来的泛型(或者非特化)模板中已经隐式声明的实例提供另一种定义。
在概念上,这是一个相对比较重要的现象,也是特化区别于重载模板的关键之处。

全特化

template <> class mycontainer <char> { ... };

First of all, notice that we precede the class template name with an empty template<> parameter list. This is to explicitly declare it as a template specialization.

But more important than this prefix, is the specialization parameter after the class template name. This specialization parameter itself identifies the type for which we are going to declare a template class specialization (char). Notice the differences between the generic class template and the specialization:

注意一下class template和specialization之间的区别:第一个是template class,第二个是specialization, 全特化

template <class T> class mycontainer { ... };
template <> class mycontainer <char> { ... };

The first line is the generic template, and the second one is the specialization.

When we declare specializations for a template class, we must also define all its members, even those exactly equal to the generic template class, because there is no “inheritance” of members from the generic template to the specialization.>
全特化是模板的一个唯一特例,指定的模板实参列表必须和相应的模板参数列表一一对应。
全特化示例 ```cpp

include

using namespace std;

template class A{ public: void function(T1 value1, T2 value2){ cout<<”value1 = “<<value1<<endl; cout<<”value2 = “<<value2<<endl; } };

template<> class A{ // 类型明确化,为全特化类 public: void function(int value1, double value2){ cout<<”intValue = “<<value1<<endl; cout<<”doubleValue = “<<value2<<endl; } };

int main(){ A a; a.function(12, 12.3); return 0; }

<a name="GDMd3"></a>
#### 偏特化
偏特化感觉像是介于普通模板和全特化之间,只存在部分的类型明确化,而非将模板唯一化。<br />再次划重点 函数模板不能被偏特化。

```cpp
#include <iostream>
using namespace std;

template<typename T1, typename T2>
class A{
    public:
        void function(T1 value1, T2 value2){
            cout<<"value1 = "<<value1<<endl;
            cout<<"value2 = "<<value2<<endl;
        }
};

template<typename T>
class A<T, double>{ // 部分类型明确化,为偏特化类
    public:
        void function(T value1, double value2){
            cout<<"Value = "<<value1<<endl;
            cout<<"doubleValue = "<<value2<<endl;
        }
};

int main(){
    A<char, double> a;
    a.function('a', 12.3);
    return 0;
}

模板类调用优先级

对主版本模板类、全特化类、偏特化类的调用优先级从高到低进行排序是:
全特化类>偏特化类>主版本模板类
这样的优先级顺序对性能也是最好的。

总结

  • 类模板和函数模板都可以被全特化;
  • 类模板能偏特化,不能被重载;
  • 函数模板全特化,不能被偏特化。

parameter pack

A template parameter pack is a template parameter that accepts zero or more template arguments (non-types, types, or templates). A function parameter pack is a function parameter that accepts zero or more function arguments. A template with at least one parameter pack is called a variadic template.

Template parameter pack 允许tempalte的parameter可以是0个或是多个

//Syntax

type ... Args(optional)
typename|class ... Args(optional)
template < parameter-list > typename(C++17)|class ... Args(optional)    

//A variadic class template can be instantiated with any number of template arguments:
template<class ... Types> struct Tuple {};
Tuple<> t0;           // Types contains no arguments
Tuple<int> t1;        // Types contains one argument: int
Tuple<int, float> t2; // Types contains two arguments: int and float
Tuple<0> error;       // error: 0 is not a type

Example

template<class ... Types> void f(Types ... args);
f();       // OK: args contains no arguments
f(1);      // OK: args contains one argument: int
f(2, 1.0); // OK: args contains two arguments: int and double
template<typename... Ts, typename U> struct Invalid; // Error: Ts.. not at the end

template<typename ...Ts, typename U, typename=void>
void valid(U, Ts...);     // OK: can deduce U
// void valid(Ts..., U);  // Can't be used: Ts... is a non-deduced context in this position

valid(1.0, 1, 2, 3);      // OK: deduces U as double, Ts as {int,int,int}

Function Argument List Example

f(&args...); // expands to f(&E1, &E2, &E3)
f(n, ++args...); // expands to f(n, ++E1, ++E2, ++E3);
f(++args..., n); // expands to f(++E1, ++E2, ++E3, n);
f(const_cast<const Args*>(&args)...);
// f(const_cast<const E1*>(&X1), const_cast<const E2*>(&X2), const_cast<const E3*>(&X3))
f(h(args...) + args...); // expands to 
// f(h(E1,E2,E3) + E1, h(E1,E2,E3) + E2, h(E1,E2,E3) + E3)
#include <iostream>

void tprintf(const char* format) // base function
{
    std::cout << format;
}

template<typename T, typename... Targs>
void tprintf(const char* format, T value, Targs... Fargs) // recursive variadic function
{
    for ( ; *format != '\0'; format++ ) {
        if ( *format == '%' ) {
           std::cout << value;
           tprintf(format+1, Fargs...); // recursive call
           return;
        }
        std::cout << *format;
    }
}

int main()
{
    tprintf("% world% %\n","Hello",'!',123);
    return 0;
}

//Output
Hello world! 123

模版的优缺点

优点:

  • reducing the repetition of code (generic containers, algorithms)
  • reducing the repetition of code advanced (MPL and Fusion)
  • static polymorphism (=performance) and other compile time calculations
  • policy based design (flexibility, reusability, easier changes, etc)
  • increasing safety at no cost (i.e. dimension analysis via Boost Units, static assertions, concept checks)
  • functional programming (Phoenix), lazy evaluation, expression templates (we can create Domain-specific embedded languages in C++, we have great Proto library, we have Blitz++)
  • other less spectacular tools and tricks used in everyday life:
    • STL and the algorithms (what’s the difference between for and for_each)
    • bind, lambda (or Phoenix) ( write clearer code, simplify things)
    • Boost Function (makes writing callbacks easier)
    • tuples (how to genericly hash a tuple? Use Fusion for example…)
    • TBB (parallel_for and other STL like algorithms and containers)
  • Can you imagine C++ without templates? Yes I can, in the early times you couldn’t use them because of compiler limitations.
  • Would you write in C++ without templates? No, as I would lose many of the advantages mentioned above.

缺点:

  • Compilation time (for example throw in Sprit, Phoenix, MPL and some Fusion and you can go for a coffee)
  • People who can use and understand templates are not that common (and these people are useful)
  • People who think that they can use and understand templates are quite common (and these people are dangerous, as they can make a hell out of your code. However most of them after some education/mentoring will join the group mentioned in the previous point)
  • template export support (lack of)
  • error messages could be less cryptic (after some learning you can find what you need, but still…)

函数模版和类模版的区别

实例化方式不同:函数模板实例化由编译程序在处理函数调用时自动完成,类模板实例化需要在程序中显示指定。
实例化的结果不同:函数模板实例化后是一个函数,类模板实例化后是一个类。
默认参数:类模板在模板参数列表中可以有默认参数。
特化:函数模板只能全特化;而类模板可以全特化,也可以偏特化。
调用方式不同:函数模板可以隐式调用,也可以显示调用;类模板只能显示调用。

#include<iostream>

using namespace std;

template <typename T>
T add_fun(const T & tmp1, const T & tmp2){
    return tmp1 + tmp2;
}

int main(){
    int var1, var2;
    cin >> var1 >> var2;
    cout << add_fun<int>(var1, var2); // 显示调用

    double var3, var4;
    cin >> var3 >> var4;
    cout << add_fun(var3, var4); // 隐式调用
    return 0;
}

//

可变参数模版

可变参数模板:接受可变数目参数的模板函数或模板类。将可变数目的参数被称为参数包,包括模板参数包和函数参数包。

模板参数包:表示零个或多个模板参数;
函数参数包:表示零个或多个函数参数。
用省略号来指出一个模板参数或函数参数表示一个包,在模板参数列表中,class… 或 typename… 指出接下来的参数表示零个或多个类型的列表;一个类型名后面跟一个省略号表示零个或多个给定类型的非类型参数的列表。当需要知道包中有多少元素时,可以使用 sizeof… 运算符。

template <typename T, typename... Args> // Args 是模板参数包
void foo(const T &t, const Args&... rest); // 可变参数模板,rest 是函数参数包
#include <iostream>

using namespace std;

template <typename T>
void print_fun(const T &t)
{
    cout << t << endl; // 最后一个元素
}

template <typename T, typename... Args>
void print_fun(const T &t, const Args &...args)
{
    cout << t << " ";
    print_fun(args...);
}

int main()
{
    print_fun("Hello", "wolrd", "!");
    return 0;
}
/*运行结果:
Hello wolrd !

*/

说明:可变参数函数通常是递归的,第一个版本的 print_fun 负责终止递归并打印初始调用中的最后一个实参。第二个版本的 print_fun 是可变参数版本,打印绑定到 t 的实参,并用来调用自身来打印函数参数包中的剩余值。

Tips

函数模版的实例化是由编译程序在处理函数调用时自动完成的,而类模版的实例化必须由程序员在程序中显示地指定
类模版的成员函数都是模版函数
类模版不能直接使用,必须先实例化为相应的模版类,然后定义类模板类的对象
函数模版不能直接使用,需要实例化为模版函数后才能使用

参考资料

https://blog.csdn.net/lyn_00/article/details/83548629
https://stackoverflow.com/questions/622659/what-are-the-good-and-bad-points-of-c-templates
https://en.cppreference.com/w/cpp/language/parameter_pack