《Effective Modern C++》读书笔记(1) -- 模板类型推导(template type deduction)

前段时间看了《Effective Modern C++》这本书,收获颇多,书中讲解了许多C++11/14的特性,都是之前不太了解或者模糊的,看了之后茅塞顿开,强烈建议C++学习者看一看。

现在是第二遍,打算把之前看的总结一下,本文大量引用了原书的内容,如有不适,请提出。

前言

《Effective Modern C++》开头说到:

C++98 had a single set of rules for type deduction: the one for function templates. C++11 modifies that ruleset a bit and adds two more,one for auto and one for decltype. C++14 then extends the usage contexts in which auto and decltype may be employed.

正如上面所说的,C++11/14中的新增的两个重要成分autodecltype,它们在类型推导里起了很大的作用。这一节不讲这两个,下一节讲auto。

C++中的类型推导

在function template中,例如:

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

当我们调用:

f(expr);                            // call f with some expression

在编译期的时候,编译器通过调用expr来推导两个类型:一个是T,另一个是ParamType。(这里顺便说下,template的类型推导是在编译期完成的)

因此,这里就有三种情况

  • paramType 是一个指针(pointer)或者一个引用(reference),但是不是通用引用(universal reference)
  • paramType 是一个通用引用(universal reference)
  • paramType 既不是指针(pointer)也不是引用(reference)

注:通用引用(universal reference)是Scott Meyers提出的说法。

a universal reference’s declared type is T&&

paramType是一个引用或者一个指针,但是不是通用引用

paramType是一个引用

例如:

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

当我们写下:

int x = 27;
const int cx = x;
const int& rx = x;

函数调用如下:

  1. f(x)
    xint类型,T推导为int类型,param的类型为int&类型

  2. f(cx)
    cxconst int类型,T推导为const int类型,param的类型为const int&类型

  3. f(rx)
    rxconst int&类型,T推导为const int类型,param的类型为const int&类型

第一个函数调用,相信大家都没什么疑问。第二个函数调用,相信有很大一部分人会半懂不懂,即便他推导正确!所以现在讲下第二个函数调用。

对于f(cx)的调用,cx是一个const int类型,由于paramType是一个引用类型,在C++中,引用就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样。cx作为实参传进函数的时候,形参param也是就是cx对象,由于cx是一个不可变变量,所以引用也是不可变的。这就是为什么T推导为const int类型。而不是说cx的类型为const int所以,Tconst int类型。

理解了这点,我们可以提前举个例子,例如:

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

此时

int x = 27;
const int cx = x;

我们调用f(cx)的时候,T的类型是int,而不是const int

所以,这里有个很重要的知识点:将一个const对象传递给一个带有&参数的模板是安全的,对象的const属性将会变为T的类型推导的一部分。

对于第三个函数调用,类型推导会将rx的引用部分忽视。

如果将代码改为:

template <typename T>
void f(const T& param);         // 区别于前面的一点就是这里加了const

当我们写下:

int x = 27;
const int cx = x;
const int& rx = x;

f(x),f(cx),f(rx)的T和param推导如上,这里就不写出来了,有兴趣的可以自己做下。

paramType是一个指针

例如:

template <typename T>
void f(T* param);           // 区别于前面的一点就是这里加了const

当我们写下:

int x = 27;
const int* px = &x;

函数调用如下:

  1. f(x)
    xint类型,T推导为int类型,param的类型为int*类型

  2. f(px)
    cxconst int*类型,T推导为const int类型,param的类型为const int*类型

paramType是一个通用引用

首先,我们要明白什么是左值,什么是右值。如果不清楚的话,请自行查阅资料。

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

// ...

f(expr);

这里有两条规则:
1. 如果expr是一个左值(lvalue),T和paramType的类型推导为左值引用
2. 如果expr是一个右值(rvalue),正常(”normal”)的类型推导规则

例如:

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

当我们写下:

int x = 27;
const int cx = x;
const int& rx = x;

函数调用如下:

  1. f(x)
    x是一个左值,Tint&paramint&

  2. f(cx)
    cx是一个左值,Tconst int&paramconst int&

  3. f(rx)
    rx是一个左值,Tconst int&paramconst int&

  4. f(27)
    27是一个右值,Tintparamint&&

paramType既不是引用也不是指针

传参既不是指针(pass-by-pointer)也不是引用(pass-by-reference),那么就是传值(pass-by-value)。

例如:

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

由于是传值,所以也意味着param是一个拷贝后的临时对象。

所以,两条准则:
1. 如果expr的类型是一个引用,忽略它的引用
2. 如果expr的类型中含有constvolatile,也忽略它(注意,这里的const指的是顶层const)。

当我们写下:

int x = 27;
const int cx = x;
const int& rx = x;
const char* const ptr = "Fun with pointers";

函数调用如下:

  1. f(x)
    xintTintparamint

  2. f(cx)
    cxconst intTintparamint

  3. f(rx)
    rxconst int&Tintparamint

  4. f(ptr)
    ptrconst char* constTconst char*paramconst char*

这里主要说下第四个调用,对于ptr的类型来说,

const char* const ptr : 左边的const 是一个底层const,右边的const是一个顶层const。

C++中,顶层const(top-level const)表示指针本身是个常数,而底层const(low-level const)表示指针所指对象是个常数,根据前面的规则,传值时,参数是拷贝后的临时对象,所以顶层引用在这里就不起作用了。

数组参数

当我们通过传值调用函数的时候,如过参数是一个数组,会有许多不同。例如:

const char name[] = "J. P. Briggs";     // const char[13]

const char* ptrToName = name;       // pointer

当我们调用f(name)时,T将会被推导为const char*。对于C/C++的代码:

void myFunc(int param[]);
void myFunc(int* param);

这里两个函数是一样的。知道这一点后,我们就能够理解为什么T会被推导为const char*

it fosters the illusion that array and pointer types are the same.

所以,要推导为数组,我们需要加一个引用!例如:

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

f(name);

此时T的类型为const char[13]

书中列出的一个代码用于得出输出数组的大小,代码如下:

template <typename T, std::size_t N>
constexpr std::size_t arraySize(T (&)N) noexcept
{
    return N;
}
函数参数

例如:

void someFunc(int, double);

template <typename T>
void f1(T param);

template <typename T>
void f2(T& param);

当我们调用:

f1(someFunc);           // param's type is void (*)(int, double);

f2(someFunc)            // param's type is void (&)(int, double);

你可能感兴趣的:(C/C++,c++,template)