4.4.2 使用追踪返回类型的函数
追踪返回类型的函数和普通函数的声明最大的区别在于返回类型的后置。在一般情况下,普通函数的声明方式会明显简单于最终返回类型。比如:
int func(char*a,int b);
这样的书写会比下面的书写少上不少。
auto func(char*a,int b)->int;
不过有的时候,追踪返回类型声明的函数也会带给大家一些意外,比如代码清单4-29所示的这个例子。
代码清单4-29
class OuterType{
struct InnerType{int i;};
InnerType GetInner();
InnerType it;
};
//可以不写OuterType::InnerType
auto OuterType::GetInner()->InnerType{
return it;
}
//编译选项:g++ -std=c++11 4-4-1.cpp
在代码清单4-29中,可以看到我们使用最终返回类型的时候,InnerType不必写明其作用域。这对于讨厌写很长作用域的程序员来说,也算得上是一个好消息。
如我们刚才提到的,返回类型后置,使模板中的一些类型推导就成为了可能。我们可以看看代码清单4-30所示的使用追踪返回类型的例子。
代码清单4-30
include <iostream>
using namespace std;
template<typename T1,typename T2>
auto Sum(const T1&t1,const T2&t2)->decltype(t1+t2){
return t1+t2;
}
template<typename T1,typename T2>
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
}
//编译选项:g++ -std=c++11 4-4-2.cpp
在代码清单4-30的例子中,我们定义了两个模板函数Sum和Mul,它们的参数的类型和返回值都在实例化时决定。而由于main函数中还使用了auto,整个例子中没有看到一个“具体”的类型声明。事实上,这段代码尤其是主函数,看起来有点像是一个动态类型语言的代码,而不像是一个有着严格静态类型的C++的代码。当然,这一切都要归功于类型推导帮助下的泛型编程。程序员在编写代码时无需关心任何时段的类型选择,编译器会合理地进行推导,而简单程序的书写也由此得到了极大的简化。
除了解决以上所描述的问题,追踪返回类型的另一个优势是简化函数的定义,提高代码的可读性。这种情况常见于函数指针中。我们可以看一下代码清单4-31所示的例子。
代码清单4-31
include <type_traits>
include <iostream>
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<decltype(pf),decltype(pf1)>::value<<endl;//1
}
//编译选项:g++ -std=c++11 4-4-3.cpp
在代码清单4-31中,定义了两个类型完全一样的函数pf和pf1。其返回的都是一个函数指针。而该函数指针又指向一个返回函数指针的函数。这一点通过is_same的成员value已经能够确定了(参见4.1.1)。而仔细看一看函数类型的声明,可以发现老式的声明法可读性非常差。而追踪返回类型只需要依照从右向左的方式,就可以将嵌套的声明解析出来。这大大提高了嵌套函数这类代码的可读性。
除此之外,追踪返回类型也被广泛地应用在转发函数中,如代码清单4-32所示。
代码清单4-32
include <iostream>
using namespace std;
double foo(int a){
return(double)a+0.1;
}
int foo(double b){
return(int)b;
}
template<class T>
auto Forward(T t)->decltype(foo(t)){
return foo(t);
}
int main(){
cout<<Forward(2)<<endl;//2.1
cout<<Forward(0.5)<<endl;//0
}
//编译选项:g++ -std=c++11 4-4-4.cpp
代码清单4-32中,我们可以看到,由于使用了追踪返回类型,可以实现参数和返回类型不同时的转发。
追踪返回类型还可以用在函数指针中,其声明方式与追踪返回类型的函数比起来,并没有太大的区别。比如:
auto(*fp)()->int;
和
int(*fp)();
的函数指针声明是等价的。同样的情况也适用于函数引用,比如:
auto(&fr)()->int;
和
int(&fr)();
的声明也是等价的。
除了以上所描述的函数模板、普通函数、函数指针、函数引用以外,追踪返回类型还可以用在结构或类的成员函数、类模板的成员函数里,其方法大同小异,这里不一一举例了。另外,没有返回值的函数也可以被声明为追踪返回类型,程序员只需要将返回类型声明为void即可。