模板实参推断

什么是模板实参推断?

从函数实参来确定模板实参的过程被称为模板实参推断(template argument deduction);在模板实参推断的过程中,编译器使用函数调用中的实参类型来寻找模板实参,用这些模板实参生成的函数版本与给定的函数调用匹配。

1.1 类型转换与模板类型参数

如果一个函数使用了模板类型参数,那么它的初始化规则将不再是普通函数的初始化规则;而是只有有限的几种类型转换会自动地应用这些实参。编译器通常不是对实参进行转换,而是生成一个新的模板实例。

  • const 转换:可以将一个非const对象的应用或者指针传递给一个const的引用或指针形参。
  • 数组或函数指针转换:如果函数形参不是引用类型,则可以对数组或函数类型的实参应用正常的指针转换。

1.2 函数模板显示实参

对于有些函数模板在调用的时候必须提供一个显示模板实参,如:

// 编译器无法推断T1
template<typename T1, typename T2, typename T3>
T1 func(T2, T3);

值得注意的是,显示模板实参按从左至右的顺序来对应的模板参数匹配。

1.3 尾置返回类型与类型转换

当用户不确定返回类型时用显示模板实参表示模板函数返回类型是很有效的;但是有些情况下,显示指定模板实参反而会增加负担。比如这样:

template <typename It>
??? &func(It beg, It end) {
      ...  // 处理序列
      return *beg;  // 返回序列中的一个元素引用
}

我们需要知道 *beg 的返回类型,用 decltype(*beg)  获取即可,但是编译器在遇到函数参数列表之前,beg是不存在;所以我们必须使用尾置返回类型。

template <typename It>
auto func(It beg, It end) -> decltype(*beg)
{
      ...  // 处理序列
      return *beg;  // 返回序列中的一个元素引用
}

解引用运算符返回一个左值,因此decltype推断的类型为beg表示的元素的类型的引用。

ok,这个问题解决了,但是又面临一个新问题,我们对于传递的参数类型几乎一无所知。但是标准库给我提供的类型转换模板,定义在头文件type_traits中,使得我们可以获得元素类型。使用remove_reference::type 脱去引用,剩下元素类型本身。(关于该模板详情看元编程)

template <typename It>
auto func(It beg, It end) -> typename remove_reference<decltype(*beg)>::type
{
      ...  // 处理序列
      return *beg;  // 返回序列中的一个元素引用
}

// 这样返回类型就不是引用了,而是这个元素类型本身。

注意一下为什么要使用 typename,因为编译器默认通过作用域访问的是名字而不是类型,这里 type 是一个类成员,而该类依赖于一个模板参数,使用typename告知编译器,type表示一个类型。

1.4 函数指针和实参推断

这里主要注意一下避免产生调用歧义,一般用显示模板参数消除即可。即当参数是一个函数模板实例的地址时,程序上下文必须满足:对每个模板参数,能唯一确定其类型或值。

1.5 引用和模板实参推断

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

// p是一个模板类型参数T的引用

这里有两点非常重要:编译器会应用正常的引用绑定规则;const 是底层的,不是顶层的。(引用类型中的const是底层的,顺便说一句,参数传递中当实参初始化形参时会忽略顶层const。)

1.5.a 左值引用函数参数推断类型

模板类型参数是一个普通的(左值)引用时,只能传递一个左值。若实参是const 的, 则T将会被推断为 const类型:

template <typename T> void f1(T&);   // 实参必须是一个左值

f1(i);   // i 是一个 int; T 是 int
f1(ci);  // ci 是一个 const int; T 是 const int
f1(43);  // 错误:传递给一个&参数的实参必须是一个左值

如果类型参数是 const T&, 那么可以传递给他任何类型的实参(一个对象,临时对象或一个字面值常量)。当 const 是函数参数本身时,T不会是一个 const 类型。

template <typename T> void f2(const T&);   // 任意类型
// f2 中参数是 cosnt &; 实参中的const 是无关的
f1(i);   // i 是一个 int; T 是 int
f1(ci);  // ci 是一个 const int; T 是 int
f1(43);  // 一个const &参数可以绑定到右值; T 是 int

1.5.b 右值引用函数参数推断类型

在实际中,模板参数的右值引用通常用于两种情况:模板转发其实参 或者 模板被重载。

template <typename T> void f3(T&&);  

f3(43);  // 实参是一个 int 类型的右值; 模板参数 T 是 int 
f3(i); // 实参是一个 int 类型的左值; 模板参数 T 是 int&

关于引用折叠;引用折叠只能应用于间接创建的引用的引用,如类型别名或模板参数。

对于一个给定类型X: 

  • X& &、X& && 和 X&& & 都折叠成 X&
  • 类型 X&& &&折叠成 X&&

有了引用折叠,则可以传递任意类型的实参给函数参数类型的右值引用。但是如果是一个左值传递,函数参数通过折叠被实例化为一个普通的左值引用;如果是一个右值传递,则函数参数被绑定到一个左值。

最后一点就是转发了,即某些函数需要将其一个或者多个实参连同类型不变地转发给其他函数。

template <typename F, typename T1, typename T2>
void flip(F f, T1 &&t1, T2 &&t2)
{
    f(std::forward<T2>(t2), std::forward<T1>(t1))  
}

void g(int &&i, int& j) 
{
   ........  // 处理
}

// std::forward 返回显示实参类型的右值引用,当用于指向模板参数类型的右值引用函数参数时,会保留实参类型所有细节

待续........

【参考】: c++primer等

原文地址:https://www.cnblogs.com/codemeta-2020/p/10642134.html