模板编程(Template Programming)

模板编程

自动推导类型

auto 

使用auto需注意以下几个事项:

  1. auto 声明的变量必须在定义时初始化
  2. 初始化的右值可以是具体的数值,也可以是表达式和函数的返回值等。
  3. auto不能作为函数的形参。
  4. auto不能直接声明数组。
  5. auto不能定于类的非静态成员函数。

auto常用于:

  1. 代替冗长复杂的变量声明。
  2. 在模板中,用于声明依赖模板参数的变量。
  3. 函数模板依赖模板参数的返回值。
  4. 用于lambda表达式。

函数模板

用一个样例就可简单解释它的用法:

template<typename T>
void Swap(T& a,T& b)
{
	T temp = a;
	a = b;
	b = temp;
}

函数模板会根据我们输入的参数类型自动推导类型T,从而生成对应的代码。

有个小细节需要注意,在C++98时使用 class来推导参数,选择可以选择typename和class,建议在函数模板中使用typename,类模板中使用class。

以下是函数模板的注意事项:

  1. 可以为类的成员函数创建模板,但不能是虚函数和析构函数。
  2. 使用函数模板时,必须明确数据类型,确保实参与函数模板能匹配上。
  3. 使用函数模板时,推导的数据类型必须适应函数模板中的代码。
  4. 如果是自动类型推导,不会发生隐式类型转换,如果显式指定了函数模板的数据类型,可以发生隐式类型转换。
  5. 函数模板支持多个通用数据类型为的参数。
  6. 函数模板支持重载,可以有非通用数据类型的参数。

解释一下第四条:

template<typename T>
T Add(const T& a,const T& b)
{
	return a + b;
}

int a = 10;
int b = 20;
char c = 'a';

std::cout<<add(a,b)<<std::endl;         //out 30
std::cout<<add(a,c)<<std::endl;         //error
std::cout<<add<int>(a,c)<<std::endl;    //out 107

函数模板的具体化(函数模板特化)

可以提供一个具体化的函数定义,当编译器找到与函数调用匹配的具体化定义时,将使用该定义,不再寻找模板。这个方法有时用来做类型判断。

template<typename T>
void isInt(T a)
{
	std::cout << "The type isn't int" << std::endl;
	//...do something
}
template<> void isInt(int a)
{
	std::cout << "The type is int" << std::endl;
	//...do something
}


int a = 1;
std::string b = "string";
isInt(a);   //out:The type is int
isInt(b);   //out:The type isn't int

关于一些命名规则:对于给定的函数名,可以有普通函数、函数模板和具体化的函数模板,以及它们的重载版本。

编译器使用各种函数的规则:

  1. 具体化优先于常规模板,可以用空模板参数强制使用函数模板。
  2. 如果希望使用函数模板,可以用空模板参数强制使用函数模板。
  3. 如果函数模板能产生更好的匹配,将优先于非模板函数。

函数模板分文件编写

函数模板只是函数的描述,没有实体,创建函数模板的代码放在头文件中。

函数模板的具体化有实体,编译的原理和普通函数一样,所以,声明放在头文件中,定义放在源文件中。

函数模板-函数模板高级

$decltype$关键字

C++11引入关键字:$decltype$ 用于查询表达式的数据类型。

decltype(expression) var;

decltype的推导规则:

  1. 如果 $expression$ 是一个没有用括号括起来的标识符,则 $var$ 的类型与该标识符的类型相同,包括 $const$ 等限定符。
  2. 如果 $expression$ 是一个函数调用,则 $var$ 的类型与函数的返回值类型相同(函数不能返回void,但能返回void *)。
  3. 如果 $expression$ 是一个左值(能取地址且在第一种情况之外)、或者用括号括起来的标识符,那么 $var$ 的类型是 $expression$ 的引用。
  4. 如果均不满足上述条件,那么 $var$ 的类型与 $expression$ 相同。

如下:

void test()
{

	int a = 10;
	decltype(a) dec_a;	//int
    decltype((a)) dec_a_ = a;	//int&

	int* b = &a;
	decltype(b) dec_b;	//int *

	int& c = a;
	decltype(c) dec_c;	//error:decltype之会返回类型,所以别忘了引用类型必须初始化

    decltype(func()) dec_func;	//int,并且不会调用func()

	decltype(func) dec_func_name;	//int()

	decltype(func) *dec_func_pointer = func;	//int(*)()
	dec_func_pointer();	//out:func

	decltype((func)) dec_func_ = func;	//int(&)()
	dec_func_();	//out:func
}

decltype与auto的区别

decltype不会执行表达式,而auto会,以上面的func举例:

auto auto_a = func();   //out:func
decltype(func()) dec_func;  //nothing output

函数后置返回类型

int func(int x, double y);

等同于:

auto func(int x, double y) -> int;

可以理解为 $auto$ 在其中的作用为一个占用符,函数定义也可以这么写。


注:从C++14后,如下可以省略decltype

template<typename T1, typename T2>
auto func(T1 x, T2 y) -> decltype(x+y)
{
    decltype(x+y) temp = x + y;
    return temp;
}

可省略为:

template<typename T1, typename T2>
auto func(T1 x, T2 y)
{
    decltype(x+y) temp = x + y;
    return temp;
}

类模板

template<class T1, class T2>
class Entity
{
private:
    T1 m_t1;
    T2 m_t2;
public:
    Entity(T1 t1, T2 t2)
        :m_t1(t1),m_t2(t2)
    {
        ...
    }
    ...
}

函数模板建议用 $typename$ 描述通用数据类型,类模板建议使用 $class$。

注:

  1. 在创建对象的时候,必须指明具体的数据类型。
  2. 使用类模板时,数据类型必须适应类模板中的代码。
  3. 类模板可以为通用参数指定缺省的数据类型(C++11标准的函数模板也可以)
template<class T1, class T2 = std::string>  //当Entity<int> entity; 中T2选择默认的string参数
class Entity
{
private:
    T1 m_t1;
    T2 m_t2;
public:
    Entity(T1 t1, T2 t2)
        :m_t1(t1),m_t2(t2)
    {
        ...
    }
    ...
}
  1. 类的成员函数可以在类外实现(但是不能在类外部指定默认参数,这是一个UB行为)。
  2. 可以用 $new$ 创建模板对象(但必须指明参数类型)。
  3. 在程序中,模板类的成员函数使用了才会创建。