c++11-17 模板核心知识—— 理解模板参数推导规则

Posted 红宸笑

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c++11-17 模板核心知识—— 理解模板参数推导规则相关的知识,希望对你有一定的参考价值。

  • Case 1 : ParamType 是一个指针或者引用,但不是 universal reference
    • T&
    • const T&
    • T*
  • Case 2 : ParamType 是 Universal Reference
    • 注意区别 Universal Reference 与右值引用
  • Case 3 : ParamType 既不是指针也不是引用
  • 数组作为参数
    • ParamType 按值传递
    • ParamType 为引用类型
  • 函数作为参数

首先我们定义一下本文通用的模板定义与调用:

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

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

在编译阶段使用expr来推断ParamTypeT这两个类型。这两个类型通常不同,因为ParamType会有const和引用等修饰。例如:

template<typename T>
void f(const T& param)
;      // ParamType is const T&
int x = 0;
f(x);      // call f with an int

这里,T 被推断成int,但是ParamType的类型是const T&

直觉下T的类型应该和expr的一样,比如上面的例子中,exprT的类型都是int。但是会有一些例外情况:T的类型不仅依赖expr,还依赖ParamType。总共分为三大类:

  • ParamType是一个指针或者引用,但不是 universal reference(或者叫 forwarding references).
  • ParamType是一个 universal reference
  • ParamType既不是指针也不是引用。

Case 1 : ParamType 是一个指针或者引用,但不是 universal reference

  • 如果 expr是一个引用,忽略其引用部分。
  • 比较 exprParamType的类型来决定 T的类型。

T&

template<typename T>
void f(T& param)
;       // param is a reference

......
int x = 27;                  // x is an int
const int cx = x;       // cx is a const int
const int& rx = x;     // rx is a reference to x as a const int

// call f
f(x);            // T is int, param's type is int&
f(cx);          // T is const int,  param's type is const int&
f(rx);         // T is const int,  param's type is const int&

上面例子是左值引用,但是这点对右值引用也适用。注意第三点,const修饰符依旧保留。 这和普通函数的类似调用有区别:

void f(int &x){

}

...
const int x  = 10;
f(x);       // error

const T&

如果给ParamType加上const,情况也没有太大变化:

template<typename T>
void f(const T& param)
;        // param is now a ref-to-const

......
int x = 27;                // as before
const int cx = x;     // as before
const int& rx = x;    // as before

......
f(x);         // T is int, param's type is const int&
f(cx);     // T is int, param's type is const int&
f(rx);      // T is int, param's type is const int&

T*

改为指针也一样:

template<typename T>
void f(T* param)
// param is now a pointer

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

f(&x);               // T is int, param's type is int*
f(px);              // T is const int, param's type is const int*

Case 2 : ParamType 是 Universal Reference

  • 如果 expr是左值,那么 TParamType会被推断为左值引用。
  • 如果 expr是右值,那么就是 Case 1 的情况。
template<typename T>
void f(T&& param)
;       // param is now a universal reference

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

调用:

f(x);          // x is lvalue, so T is int&, param's type is also int&
f(cx);         // cx is lvalue, so T is const int&, param's type is also const int&
f(rx);        // rx is lvalue, so T is const int&, param's type is also const int&
f(27);        // 27 is rvalue, so T is int, param's type is therefore int&&

如果之前了解过完美转发和折叠引用的概念,结合 Case1,这一个规则还是比较好理解的。

注意区别 Universal Reference 与右值引用

这两点需要区分清楚,比如:

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


template<typename T>
void f(std::vector<T>&& param)
;       // rvalue reference

有一个通用规则 :universal reference会有类型推断的过程。具体在后面的单独文章会讲,跟这篇文章的主题关系不大,这里稍微提一下 : )

Case 3 : ParamType 既不是指针也不是引用

这种情况就是 pass-by-value 的情况:

template<typename T>
void f(T param)
// param is now passed by value

这意味着,param 是一个被拷贝的全新对象,也就是 param 决定着 T 的类型:

  • 如果 expr是引用类型,忽略。
  • 如果 expr带有 const、volatile,忽略。
int x = 27;
const int cx = x;
const int& rx = x;
f(x);         // T's and param's types are both int
f(cx);      // T's and param's types are again both int
f(rx);      // T's and param's types are still both int

忽略 const 和 volatile 也比较好理解:参数是值拷贝,所以形参和实参其实是互相独立的。正如下面代码可以将const int传递给int,但是声明为引用则不行:

void f(int x){

}

int main() {
  const int x  = 10;

  f(x);
}

注意忽略的 const 是针对参数本身的,而不针对指针指向的 const 对象:

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

......
const charconst ptr = "Fun with pointers";       // ptr is const pointer to const object
f(ptr);             // pass arg of type const char * const

这个按照值传递的是 ptr,所以 ptr 的 const 会被忽略,但是 ptr 指向的对象依然是 const。

数组作为参数

数组类型和指针类型是两种类型,但是有时候他们是可以互换的,比如在下面这种情况下,数组会 decay 成指针:

const char name[] = "J. P. Briggs";     // name's type is const char[13]
const char * ptrToName = name;       // array decays to pointer

在普通函数中,函数形参为数组类型和指针类型是等价的:

void myFunc(int param[]);
void myFunc1(int* param);         // same function as above

但是数组作为模板参数是比较特殊的一种情况。

ParamType 按值传递

template<typename T>
void f(T param)
// template with by-value parameter

......
const char name[] = "J. P. Briggs";     // name's type is  const char[13]

f(name);           // name is array, but T deduced as const char*

这种情况下,T被推断为指针类型const char*.

ParamType 为引用类型

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

......
const char name[] = "J. P. Briggs";     // name's type is  const char[13]
f(name);             // pass array to f

现在T被推断为数组类型const char [13]ParamTypeconst char (&)[13],这种情况是很特殊的,要与ParamType按值传递区分开。

我们可以利用上面这种特性定义一个模板来推断数组的大小,这种用法还蛮常见的:

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

......
int keyVals[] = { 1379112235 };
std::array<int, arraySize(keyVals)> mappedVals;

函数作为参数

上面讨论的关于数组的情况同样适用于函数作为参数,函数类型同样也可以decay成函数指针:

void someFunc(intdouble);        // someFunc is a function;type is void(int, double)
template <typename T> void f1(T param);     // in f1, param passed by value
template <typename T> void f2(T &param);    // in f2, param passed by ref
f1(someFunc);        // param deduced as ptr-to-func; type is void (*)(int, double)
f2(someFunc);      // param deduced as ref-to-func; type is void (&)(int, double)

不过这在平时应用中也没有太大差别。

(完)


以上是关于c++11-17 模板核心知识—— 理解模板参数推导规则的主要内容,如果未能解决你的问题,请参考以下文章

为啥不能推导出嵌套在模板类中的枚举的模板参数?

C/C++中的可变参数和可变参数模板

C++11学习记录:核心语言功能特性

C++11学习记录:核心语言功能特性

C++11学习记录:核心语言功能特性

C++到底有多少种魔法来实现println?