玩命加载中 . . .

01-理解模板类型推导


考虑这样的函数模板

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

f(expr);                        //从expr中推导T和ParamType

有三种情况需要考虑

情况一:ParamType是一个指针或引用,但不是通用引用

最简单的情况是ParamType是一个指针或者引用,但非通用引用。在这种情况下,类型推导会这样进行:

  1. 如果expr的类型是一个引用,忽略引用部分
  2. 然后expr的类型与ParamType进行模式匹配来决定T

比如模板是这样的

template<typename T>
void f(T& param);               //param是一个引用

调用的时候

int x = 27;             // x是int
const int cx = x;       // cx是const int
const int& rx = x;      // rx是指向作为const int的x的引用

f(x);                   // T是int, param的类型是int&
f(cx);                  // T是const int, param的类型是const int&
f(rx);                  // T是const int, param的类型是const int&

这也是为什么将一个const对象传递给以T&类型为形参的模板安全的:对象的常量性constness会被保留为T的一部分

如果是右值也是一样的

如果我们将f的形参类型T&改为const T&,情况有所变化

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

int x = 27;                 // 跟之前一样
const int cx = x;           // 跟之前一样
const int& rx = x;          // 跟之前一样

f(x);                       // T是int,param的类型是const int&
f(cx);                      // T是int,param的类型是const int&
f(rx);                      // T是int,param的类型是const int&

因为现在我们假设param是reference-to-const,const不再被推导为T的一部分

如果是指针,本质上也是一样的

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

int x = 27;                 // 同之前一样
const int *px = &x;         // px是指向作为const int的x的指针

f(&x);                      // T是int,param的类型是int*
f(px);                      // T是const int,param的类型是const int*

情况二:ParamType是一个通用引用

  • 如果expr是左值,TParamType都会被推导为左值引用。第一,这是模板类型推导中唯一一种T被推导为引用的情况。第二,虽然ParamType被声明为右值引用类型,但是最后推导的结果是左值引用。
  • 如果expr是右值,就使用正常的(也就是情况一)推导规则
template<typename T>
void f(T&& param);      //param现在是一个通用引用类型
		
int x = 27;
const int cx = x;
const int & rx = cx;

f(x);                   // x是左值,所以T是int&,
                        // 引用折叠,int& && 折叠成 int&,所以param类型也是int&

f(cx);                  // cx是左值,所以T是const int&,
                        // param类型也是const int&

f(rx);                  // rx是左值,所以T是const int&,
                        // param类型也是const int&

f(27);                  // 27是右值,所以T是int,param类型就是int&&

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

template<typename T>
void f(T param);                //以传值的方式处理param
  1. 和之前一样,如果expr的类型是一个引用,忽略这个引用部分
  2. 如果忽略expr的引用性(reference-ness)之后,expr是一个const,那就再忽略const。如果它是volatile,也忽略volatilevolatile对象不常见,它通常用于驱动程序的开发中

因此

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

f(x);                       // T和param的类型都是int
f(cx);                      // T和param的类型都是int
f(rx);                      // T和param的类型都是int

具有常量性的cxrx不可修改并不代表param也是一样,因为param只是它们的拷贝

只有在传值给形参时才会忽略const(和volatile

考虑指针的顶层const和底层const情况

template<typename T>
void f(T param);                //仍然以传值的方式处理param

// ptr是一个常量指针,指向常量对象 
const char* const ptr = "Fun with pointers"; 

f(ptr);    // 传递const char * const类型的实参,param的类型是const char *

ptr自身的常量性将会被省略,也就是顶层的const被忽略,底层的const被保留,所以param的类型是const char*,不能改变所指向的对象

数组实参

数组可以退化为指针

const char name[] = "J. P. Briggs"; // name的类型是const char[13]

const char* ptrToName = name;       // 数组退化为指针

这两种类型(const char*const char[13])是不一样的

如果给一个函数传递数组,形参可以是这样的

void myFunc(int param[]);

但是数组声明会被视作指针声明,这意味着myFunc的声明和下面声明是等价的

void myFunc(int* param);

因为数组形参会视作指针形参,所以传值给模板的一个数组类型会被推导为一个指针类型

template<typename T>
void f(T param);        // 传值形参的模板

f(name);                // name是一个数组,但是T被推导为const char*

但是现在难题来了,虽然函数不能声明形参为真正的数组,但是可以接受指向数组的引用

template<typename T>
void f(T& param);       //传引用形参的模板

f(name);    // T被推导为const char[13],所以param是const char(&)[13]

这样也可以创建一个模板函数来推导出数组的大小

//在编译期间返回一个数组大小的常量值
template<typename T, size_t N>
constexpr size_t arraySize(T (&)[N]) noexcept
{
    return N;                                         
}                                                      

函数实参

函数类型也会退化为一个函数指针

void someFunc(int, double);         // someFunc是一个函数,
                                    // 类型是void(int, double)

template<typename T>
void f1(T param);                   // 传值给f1

template<typename T>
void f2(T& param);                  // 传引用给f2

f1(someFunc);                       // param被推导为指向函数的指针,
                                    // 类型是void(*)(int, double)
f2(someFunc);                       // param被推导为指向函数的引用,
                                    // 类型是void(&)(int, double)

请记住:

  • 在模板类型推导时,有引用的实参会被视为无引用,他们的引用会被忽略
  • 对于通用引用的推导,左值实参会被特殊对待
  • 对于传值类型推导,constvolatile实参会被认为是non-const的和non-volatile的
  • 在模板类型推导时,数组名或者函数名实参会退化为指针,除非它们被用于初始化引用

文章作者: kunpeng
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 kunpeng !
  目录