一、引入
前面反复提到追踪返回类型配合auto和decltype会真正释放泛型编程的能力。
前面的例子中,我们先是通过auto自动推导参数计算后的类型,但是因为无法应用于返回值,所以只能限定返回值为double。
template<typename T1, typename T2>
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<int ,long>(a, b); // s的类型被推导为long
auto f = Sum<float,float>(c, d); // s的类型被推导为float
}
之后,在decltype中,我们通过引用参数的方式,传入返回值,但效果依旧鸡肋。
// s的类型被声明为decltype(t1 + t2)
template<typename T1, typename T2>
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<typename T1, typename T2>
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<typename T1, typename T2>
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 <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
}
简化函数的定义
这一条通常是对于多层函数而言(更像是面试题)
#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
}
定义了两个类型完全一样的函数pf和pf1。其返回的都是一个函数指针。而该函数指针又指向一个返回函数指针的函数。这一点通过is_same的成员value已经能够确定了。
而仔细看一看函数类型的声明,可以发现老式的声明法可读性非常差。而追踪返回类型只需要依照从右向左的方式,就可以将嵌套的声明解析出来。这大大提高了嵌套函数这类代码的可读性。
应用于转发函数
追踪返回类型也被广泛地应用在转发函数中
#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
}
我们可以看到,由于使用了追踪返回类型,可以实现参数和返回类型不同时的转发。
函数指针与函数引用
追踪返回类型还可以用在函数指针中,其声明方式与追踪返回类型的函数比起来,并没有太大的区别。比如:
auto (*fp)() -> int;
和
int (*fp)();
的函数指针声明是等价的。
同样的情况也适用于函数引用,比如:
auto (&fr)() -> int;
和
int (&fr)();
的声明也是等价的。