Generics Programming in C++ (NC)

Function Templates (C++98)


Function Generalization: Avoiding Copy-N-Paste

C 语言不支持函数重载(function overloading),这就意味着我们必须给不同的函数起不同的函数名,这对于完成相同任务但接收不同参数的函数来说非常麻烦。而 C++ 从一开始就支持函数重载,允许代码实现静态多态性。通过传递不同的参数,编译器会调用相应的函数。

函数重载当然是一个伟大的特性,但是如果我们要重载许多函数,我们就需要复制粘贴许多遍。但使用函数重载会给我们带来一些潜在问题。除了复制粘贴许多遍之外,我们还可能碰到一些未定义重载函数的情况,比如下面我们使用 long long sum_ll = sum(5342ll, 5864ll); 我们并没有定义相关的函数重载,编译器就不知道调用哪个函数。

函数重载当然是一个伟大的特性,但是如果我们要重载许多函数,我们就需要复制粘贴许多遍。但使用函数重载会带来一些潜在问题。除了需要复制粘贴许多遍之外,我们还可能碰到一些未定义重载函数的情况,比如下面我们使用 long long sum_ll = sum(5342ll, 5864ll); 我们并没有定义相关的函数重载,编译器就不知道调用哪个函数。

int add(int a, int b){
	return a + b;
}
float add(float a, float b){
	return a + b;
}
double add(double a, double b){
	return a + b;
}
// ...
int main(){
	int sum_i = add(20, 30); // Call int add(int a, int b)
	float sum_f = add(3.14f, 5.58f); // Call float add(float a, float b)
	double sum_d = sum(3.254, 2.546); // Call double add(double a, double b)
	long long sum_ll = sum(5342ll, 5864ll); // Which to call? It's ambiguous!
	return 0;
}

而在 C++98 后,函数模板(泛型函数)的出现避免了多次代码的复制粘贴。模板相当于一个你编写的蓝图,编译器在编译代码的时候会根据你提供的蓝图帮你生成重载函数。从而,你不需要记忆你到底重载了哪些函数。

// Compiler will generate function code at compile-time
// And function template is not a function btw
template<typename T>
T add(T a, T b) {
    return a + b;
}
int main() {
    int sum_i = add<int>(20, 30); // Same as add(20, 30) expicitly
    float sum_f = add<float>(3.14f, 5.58f); // Same as add(3.14f, 5.58f)
    double sum_d = add<double>(3.254, 2.546); // Same as add(3.254, 2.546)
    long long sum_ll = add<long long>(5342, 5864); // Same as add(5232ll, 5864ll)
    return 0;
}

上面的例子中,编译器帮我们实例化生成了四个重载函数。在 C++17 后,你实际上可以不再显式地提供模板重载类型,编译器会帮你推导相关的类型。

Function Template (Part II)

每个模板类型都至少被类型推断一次

multiple template parameters and non object type params

with auto

Variadic arguments and Variadic Function Templates

Abbreviated function templates

Template Specialization

Full Specialization

Partial Specialization

Time to Introduce Move Semantics

reference collapsing

Class Template (C++98)


同样,类模板也不是一个类。它是让编译器帮我们创建类的模板蓝图。

STL - you use all the time

class myContainer {
public:
	myContainer(int N) {
		m_data = new int[N];
	}
	~myContainer() {
		delete[] m_data;
	}
private:
	int* m_data;
};

class myContainer {
public:
	myContainer(int N) {
		m_data = new float[N];
	}
	~myContainer() {
		delete[] m_data;
	}
private:
	float* m_data;
};
template<typename T>
class myContainer {
public:
	myContainer(int N) {
		m_data = new T[N];
	}
	~myContainer() {
		delete[] m_data;
	}
private:
	T* m_data;
};

还可以提供一个 non-type object

#include <cstddef> // For size_t

template<typename T, size_t N>
class myContainer {
public:
    myContainer() {
        m_data = new T[N];
    }
    ~myContainer() {
        delete[] m_data;
    }
private:
    T* m_data;
};

int main() {
    myContainer<int, 5> container;
    return 0;
}

Template Class with Static Data Members

Default Parameters in Class Template

Class Template Argument Deduction (CTAD) Since C++17

typeid().name()

Alias Templates (C++11)


#include <vector>

// Without alias templates:
typedef std::vector<int> myvec_int; // C++03 alias syntax
typedef std::vector<float> myvec_float; // C++03 alias syntax

// With alias templates:
template<typename T>
using myvec = std::vector<T>; // C++11 syntax

int main(){
    myvec_int vi = {1, 2, 3, 4, 5}; // Using the old alias syntax
    myvec_float vf = {1.1f, 2.2f, 3.3f}; // Using the old alias syntax

    myvec<int> vi_alias = {6, 7, 8, 9, 10}; // Using the new alias template syntax
    myvec<float> vf_alias = {4.4f, 5.5f, 6.6f}; // Using the alias template syntax
    return 0;
}

Alias template cannot be specialized.

Variable Templates (C++14)


2.2 Non-Type Template Parameters (NTTPs)

模板的参数并不必须为一种”类型“:

template<class T, size_t N>
class Array{
	T    m_data[N];
	// ...
};

Array<foobar, 10> some_foobars;

NTTPs中的常数类型必须在编译阶段或链接时确定好,而且类型必须为:

  • 整型或枚举(最常见)
  • 指针
  • std::nullptr_t
  • 其他