Effective Modern C++:条款01

Aki 发布于 2022-12-02 255 次阅读


01:理解模板型别推导

模板的类型推导是现代C++最广泛的特性之一,了解型别推导也很重要。

模板及调用的一般形式:

template<typename T>
void f(ParamType param);

f(expr);        //从expr来推导T和ParamType的类型

情况1:ParamType是个指针或引用,但不是个万能引用

推导规则:

1.若expr具有引用类型,先将引用部分忽略。

2.尔后,对expr的类型和ParamType的类型进行模式匹配,来决定T的类型。

例如,有如下的模式:

template<class T>
void f(T& param)
{
	cout << typeid(T).name() << endl;
}

int x = 0;            // x为int
const int xx = x;    //xx为const int
const int& xxx = x;  //xxx为const int&

f(x);     //param为int&,T推导为int
f(xx);    //param为const int&,T为const int
f(xxx);   //param为const int&,T为const int

在第二个和第三个调用语句中,由于xx和xxx的值都被指明为const,所以T的类型被推导为const int,从而形参param的类型就成了const int&。

上述调用语句示例演示的都是左值引用形参,但是右值引用形参的类型推导方式是完全相同的。

若将形参类型从T&改为const T&,结果和意想中的差别不大。xx和xxx的常量性仍得到满足,但由于现在假定param具有const引用类型,T的类型推导结果中没有必要再包含const了。推导如下:

template<typename T>
void f(const T& param);       //param现在是个const &

int x = 27;         //同前
const int xx = x;   //同前
const int& xxx = x;  //同前

f(x);   //T:int, param:const int&
f(xx);  //T:int, param:const int&
f(xx);  //T:int, param:const int&

若param是个指针(或指向const对象的指针)时,推导方式本质上一样:

template<typename T>
void f(T* param);       //param现在是个指针

int x = 27;         //同前
const int *px = &x; //px:const int

f(&x);   //T:int, param:int*
f(px);  //T:const int, param:const int*

情况2:ParamType是个万能引用

  • 若expr是个左值,T和ParamType都会被推导为左值引用。这个结果有两重神奇之处:首先,这是在模板类型推导中,T被推导为引用类型的唯一情形。其次,尽管在声明时使用的是右值引用语法(T&&),它的型别推导确实左值引用。
  • 若expr是个右值,则应用“常规”(情形1中的)规则。
template<class T>
void f(T&& param)     
{
	cout << typeid(decltype((param))).name() << endl;
}

int x = 0;    // x为int
const int xx = x;  //xx为const int
const int& xxx = x;  //xxx为const int&

f(x);     //param为int&,T为int&
f(xx);  //param为const int&,T为const int&
f(xxx);  //param为const int&,T为const int&
f(20);  //param为int&&,T为int

情况3:ParamType既不是指针也不是引用

这意味着,无论传入的是什么,param都是它的一个拷贝的副本,也就是一个全新的对象。

推导规则是:

1)若expr具有引用类型,则忽略其引用部分。

2)忽略expr的引用性之后,若expr是个const对象,也忽略之。若其是个volatile对象,同忽略之。(volatile对象不常用,一般使用与实现设备驱动程序)。

int x = 0;    // x为int
const int xx = x;  //xx为const int
const int& xxx = x;  //xxx为const int&

f(x);     //param为int,T为int
f(xx);  //param为int,T为int
f(xxx);   //param为int,T为int
f(20);  //param为int,T为int

情况4:数组实参

这种情况是传入数组类型,数组类型有别于指针类型,尽管它们看起来可以互换。形成这种假象的主要原因是,在很多语境下数组会退化成其首元素的指针。下面这段代码能够编译成功,就是因为这种退化机制在发挥作用。

const char str[] = "hello,world";
const char* strPointer = str;

当将一个数组传递给模板时又会这么样呢?


template<class T>
void f(T param)      //param现在是个指针
{
	cout << typeid(decltype((param))).name() << endl;
	cout << param << endl;
}

const char str[] = "hello,world";
f(str);  

template<class T,size_t N>
constexpr size_t arraySize(T(&)[N])noexcept
{
	return N;
}

arraySize(str) = 12;

实际上传入模板是数组首元素指针。

身为一名现代C++程序员,相对于内建数组,会优先选择std::array,相对于内建数组无额外开销且提供了许多良好的接口方便使用。

情况5:并非只有数组会退化为指针,函数实参也会退化为函数指针

int func(int, int)
{
	return 0;
}

template<class T>
void f(T param)      //按值传递
{
	cout << typeid(decltype((param))).name() << endl;
	cout << param << endl;
}

f(func);   //被推导为函数指针,int(*)(int,int)

//如果模板按引用传递的话
func(func); //被推倒为int(&)(int,int); 函数引用

请记住:

1)在模板类型推导过程中,具有引用型别的实参被当成非引用型别来处理。换言之其引用性会被忽略。

2)对万能引用形参进行推导时,左值实参会被特殊处理。

3)对按值传递的形参进行推导时,若实参型别中带有const或volatile修饰词,则它们还是会被当作不带const或volatile修饰词的型别来处理。

4)在模板型别推导过程中,数组或函数型别的实参会退化成对应的指针,除了非它们被用来初始化引用。