4.4 追踪返回类型

一、引入

前面反复提到追踪返回类型配合auto和decltype会真正释放泛型编程的能力。

前面的例子中,我们先是通过auto自动推导参数计算后的类型,但是因为无法应用于返回值,所以只能限定返回值为double。

template
double Sum(T1 & t1, T2 & t2) {
    auto s = t1 + t2;    // s的类型会在模板实例化时被推导出来
    return s;
}
int main() {
    int a = 3;
    long b = 5;
    float c = 1.0f, d = 2.3f;
    auto e = Sum(a, b);       // s的类型被推导为long
    auto f = Sum(c, d);     // s的类型被推导为float
}

之后,在decltype中,我们通过引用参数的方式,传入返回值,但效果依旧鸡肋。

// s的类型被声明为decltype(t1 + t2)
template
void Sum(T1 & t1, T2 & t2, decltype(t1 + t2) & s) {
    s = t1 + t2;
}
int main() {
    int a = 3;
    long b = 5;
    float c = 1.0f, d = 2.3f;
    long e;
    float f;
    Sum(a, b, e);    // s的类型被推导为long
    Sum(c, d, f);    // s的类型被推导为float
}

那么我们为什么不能直接推导返回值类型呢,比如下面的形式

template
decltype(t1 + t2) Sum(T1 & t1, T2 & t2) {
    return t1 + t2;
}

这是因为编译器只能从左到右的读入符号,所以在推导decltype(t1+t2)的时候无法知道t1和t2的类型。

为了解决这一问题,C++11引入了追踪返回类型,其思路就是通过auto占位返回类型,并在函数声明后获取(提供或通过decltype推导)实际类型,其语法如下:

auto fun(paramList) -> returntype

而与decltype结合之后可以将上述代码改造为:

template
auto Sum(T1 & t1, T2 & t2) -> decltype(t1 + t2){
    return t1 + t2;
}

完美的释放了泛型能力。

二、使用追踪返回类型的函数

省略作用域

通常来说,普通函数声明

int func(char* a, int b);

明显比追踪返回类型的函数声明要简洁:

auto func(char*a, int b) -> int;

但是,返回类型需要带上作用域的时候,追踪返回类型反而更有优势(可能是具有与函数内相同的作用域。)

class OuterType {
	struct InnerType { int i; };
	InnerType GetInner();
	InnerType it;
};
// 可以不写OuterType::InnerType
auto OuterType::GetInner() -> InnerType {
	return it;
}
模板返回值类型推导

如前面提到的,追踪返回类型与模板结合后,模板变得更具泛型能力

#include 
using namespace std;
template
auto Sum(const T1 & t1, const T2 & t2) -> decltype(t1 + t2){
    return t1 + t2;
}
template 
auto Mul(const T1 & t1, const T2 & t2) -> decltype(t1 * t2){
    return t1 * t2;
}
int main() {
    auto a = 3;
    auto b = 4L;
    auto pi = 3.14;
    auto c = Mul(Sum(a, b), pi);
    cout << c << endl;   // 21.98
}
简化函数的定义

这一条通常是对于多层函数而言(更像是面试题)

#include 
#include 
using namespace std;
// 有的时候,你会发现这是面试题
int (*(*pf())())() {
    return nullptr;
}
// auto (*)() -> int(*) () 一个返回函数指针的函数(假设为a函数)
// auto pf1() -> auto (*)() -> int (*)() 一个返回a函数的指针的函数
auto pf1() -> auto (*)() -> int (*)() {
    return nullptr;
}
int main() {
    cout << is_same::value << endl;     // 1
}

定义了两个类型完全一样的函数pf和pf1。其返回的都是一个函数指针。而该函数指针又指向一个返回函数指针的函数。这一点通过is_same的成员value已经能够确定了。

而仔细看一看函数类型的声明,可以发现老式的声明法可读性非常差。而追踪返回类型只需要依照从右向左的方式,就可以将嵌套的声明解析出来。这大大提高了嵌套函数这类代码的可读性。

应用于转发函数

追踪返回类型也被广泛地应用在转发函数中

#include 
using namespace std;
double foo(int a) {
    return (double)a + 0.1;
}
int foo(double b) {
    return (int)b;
}
template 
auto Forward(T t) -> decltype(foo(t)){
    return foo(t);
}
int main(){
    cout << Forward(2) << endl;      // 2.1
    cout << Forward(0.5) << endl;    // 0
}

我们可以看到,由于使用了追踪返回类型,可以实现参数和返回类型不同时的转发。

函数指针与函数引用

追踪返回类型还可以用在函数指针中,其声明方式与追踪返回类型的函数比起来,并没有太大的区别。比如:

auto (*fp)() -> int;

int (*fp)();

的函数指针声明是等价的。

同样的情况也适用于函数引用,比如:

auto (&fr)() -> int;

int (&fr)();

的声明也是等价的。

你可能感兴趣的:(深入理解C++11新特性,C++11)