当前位置:嗨网首页>书籍在线阅读

21-模板函数的发展

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

8.5.6 模板函数的发展

在C++发展的早期,大多数人都没有想到模板函数和模板类会有这么强大而有用,它们甚至没有就这个主题发挥想象力。但聪明而专注的程序员挑战模板技术的极限,阐述了各种可能性。根据熟悉模板的程序员提供的反馈,C++98标准做了相应的修改,并添加了标准模板库。从此以后,模板程序员在不断探索各种可能性,并消除模板的局限性。C++11标准根据这些程序员的反馈做了相应的修改。下面介绍一些相关的问题及其解决方案。

1.是什么类型

在C++98中,编写模板函数时,一个问题是并非总能知道应在声明中使用哪种类型。请看下面这个不完整的示例:

template<class T1, class T2>
void ft(T1 x, T2 y)
{
    ...
    ?type? xpy = x + y;
    ...
}

xpy应为什么类型呢?由于不知道ft()将如何使用,因此无法预先知道这一点。正确的类型可能是T1、T2或其他类型。例如,T1可能是double,而T2可能是int,在这种情况下,两个变量的和将为double类型。T1可能是short,而T2可能是int,在这种情况下,两个变量的和为int类型。T1还可能是short,而T2可能是char,在这种情况下,加法运算将导致自动整型提升,因此结果类型为int。另外,结构和类可能重载运算符+,这导致问题更加复杂。因此,在C++98中,没有办法声明xpy的类型。

2.关键字decltype(C++11)

C++11新增的关键字decltype提供了解决方案。可这样使用该关键字:

int x;
decltype(x) y; // make y the same type as x

给decltype提供的参数可以是表达式,因此在前面的模板函数ft()中,可使用下面的代码:

decltype(x + y) xpy; // make xpy the same type as x + y
xpy = x + y;

另一种方法是,将这两条语句合而为一:

decltype(x + y) xpy = x + y;

因此,可以这样修复前面的模板函数ft():

template<class T1, class T2>
void ft(T1 x, T2 y)
{
    ...
    decltype(x + y) xpy = x + y;
    ...
}

decltype比这些示例演示的要复杂些。为确定类型,编译器必须遍历一个核对表。假设有如下声明:

decltype(expression) var;

则核对表的简化版如下:

第一步:如果expression是一个没有用括号括起的标识符,则var的类型与该标识符的类型相同,包括const等限定符:

double x = 5.5;
double y = 7.9;
double &rx = x;
const double * pd;
decltype(x) w;       // w is type double
decltype(rx) u = y;  // u is type double &
decltype(pd) v;      // v is type const double *

第二步:如果expression是一个函数调用,则var的类型与函数的返回类型相同:

long indeed(int);
decltype (indeed(3)) m; // m is type long

注意: 并不会实际调用函数。编译器通过查看函数的原型来获悉返回类型,而无需实际调用函数。

第三步:如果expression是一个左值,则var为指向其类型的引用。这好像意味着前面的w应为引用类型,因为x是一个左值。但别忘了,这种情况已经在第一步处理过了。要进入第三步,expression不能是未用括号括起的标识符。那么,expression是什么时将进入第三步呢?一种显而易见的情况是,expression是用括号括起的标识符:

double xx = 4.4;
decltype ((xx)) r2 = xx;  // r2 is double &
decltype(xx) w = xx;      // w is double (Stage 1 match)

顺便说一句,括号并不会改变表达式的值和左值性。例如,下面两条语句等效:

xx = 98.6;
(xx) = 98.6; // () don't affect use of xx

第四步:如果前面的条件都不满足,则var的类型与expression的类型相同:

int j = 3;
int &k = j
int &n = j;
decltype(j+6) i1;  // i1 type int
decltype(100L) i2; // i2 type long
decltype(k+n) i3;  // i3 type int;

请注意,虽然k和n都是引用,但表达式k+n不是引用;它是两个int的和,因此类型为int。

如果需要多次声明,可结合使用typedef和decltype:

template<class T1, class T2>
void ft(T1 x, T2 y)
{
    ...
    typedef decltype(x + y) xytype;
    xytype xpy = x + y;
    xytype arr[10];
    xytype & rxy = arr[2]; // rxy a reference
    ...
}

3.另一种函数声明语法(C++11后置返回类型)

有一个相关的问题是decltype本身无法解决的。请看下面这个不完整的模板函数:

template<class T1, class T2>
?type? gt(T1 x, T2 y)
{
    ...
    return x + y;
}

同样,无法预先知道将x和y相加得到的类型。好像可以将返回类型设置为decltype ( x + y),但不幸的是,此时还未声明参数x和y,它们不在作用域内(编译器看不到它们,也无法使用它们)。必须在声明参数后使用decltype。为此,C++新增了一种声明和定义函数的语法。下面使用内置类型来说明这种语法的工作原理。对于下面的原型:

double h(int x, float y);

使用新增的语法可编写成这样:

auto h(int x, float y) -> double;

这将返回类型移到了参数声明后面。->double被称为后置返回类型(trailing return type)。其中auto是一个占位符,表示后置返回类型提供的类型,这是C++11给auto新增的一种角色。这种语法也可用于函数定义:

auto h(int x, float y) -> double
{/* function body */};

通过结合使用这种语法和decltype,便可给gt()指定返回类型,如下所示:

template<class T1, class T2>
auto gt(T1 x, T2 y) -> decltype(x + y)
{
    ...
    return x + y;
}

现在,decltype在参数声明后面,因此x和y位于作用域内,可以使用它们。