前面已经出现了函数返回类型后置的例子,接下来我们将详细讨论C++11标准中的新语法特性:
auto foo()->int
{
return 42;
}以上代码中的函数声明等同于int foo(),只不过采用了函数返回类型后置的方法,其中auto是一个占位符,函数名后->紧跟的int才是真正的返回类型。当然,在这个例子中传统的函数声明方式更加简洁。而在返回类型比较复杂的时候,比如返回一个函数指针类型,返回类型后置可能会是一个不错的选择,例如:
int bar_impl(int x)
{
return x;
}
typedef int(*bar)(int);
bar foo1()
{
return bar_impl;
}
auto foo2()->int(*)(int)
{
return bar_impl;
}
int main() {
auto func = foo2();
func(58);
}在上面的代码中,函数foo2的返回类型不再是简单的int而是函数指针类型。使用传统函数声明语法的foo1无法将函数指针类型作为返回类型直接使用,所以需要使用typedef给函数指针类型创建别名bar,再使用别名作为函数foo1的返回类型。而使用函数返回类型后置语法的foo2则没有这个问题。同样,auto作为返回类型占位符,在->后声明返回的函数指针类型int(*)(int)即可。
C++11标准中函数返回类型后置的作用之一是推导函数模板的返回类型,当然前提是需要用到decltype说明符,例如:
template<class T1, class T2>
auto sum1(T1 t1, T2 t2)->decltype(t1 + t2)
{
return t1 + t2;
}
int main() {
auto x1 = sum1(4, 2);
}在上面的代码中,函数模板sum1有两个模板形参T1和T2,它们分别是函数形参t1和t2的类型。为了让sum1函数的返回类型由实参自动推导,这里需要使用函数返回类型后置来指定decltype说明符推导类型作为函数的返回类型。请注意,decltype(t1 + t2)不能写在函数声明前,编译器在解析返回类型的时候还没解析到参数部分,所以它对t1和t2一无所知,自然会编译失败:
decltype(t1 + t2) sum1(T1 t1, T2 t2) {…} // 编译失败,无法识别t1和t2实际上,在C++11标准中只用decltype关键字也能写出自动推导返回类型的函数模板,但是函数可读性却差了很多,以下是最容易理解的写法:
template<class T1, class T2>
decltype(T1() + T2()) sum2(T1 t1, T2 t2)
{
return t1 + t2;
}
int main() {
sum2(4, 2);
}以上代码使用decltype(T1()+T2())让编译器为我们推导函数的返回类型,其中T1()+T2()表达式告诉编译器应该推导T1类型对象与T2类型对象之和的对象类型。但是这种写法并不通用,它存在一个潜在问题,由于T1() + T2()表达式使用了T1和T2类型的默认构造函数,因此编译器要求T1和T2的默认构造函数必须存在,否则会编译失败,比如:
class IntWrap {
public:
IntWrap(int n) : n_(n) {}
IntWrap operator+ (const IntWrap& other)
{
return IntWrap(n_ + other.n_);
}
private:
int n_;
};
int main() {
sum2(IntWrap(1), IntWrap(2)); // 编译失败,IntWrap没有默认构造函数
}虽然编译器在推导表达式类型的时候并没有真正计算表达式,但是会检查表达式是否正确,所以在推导IntWrap() + IntWrap()时会报错。为了解决这个问题,需要既可以在表达式中让T1和T2两个对象求和,又不用使用其构造函数方法,于是就有了以下两个函数模板:
template<class T1, class T2>
decltype(*static_cast<T1 *>(nullptr) + *static_cast<T2 *>(nullptr)) sum3(T1 t1, T2 t2)
{
return t1 + t2;
}
template<class T>
T&& declval();
template<class T1, class T2>
decltype(declval<T1>() + declval<T2>()) sum4(T1 t1, T2 t2)
{
return t1 + t2;
}
int main() {
sum3(IntWrap(1), IntWrap(2));
sum4(IntWrap(1), IntWrap(2));
}在上面的代码中,函数模板sum3使用指针类型转换和解引用求和的方法推导返回值,其中*static_cast<T1 * >(nullptr)+ * static_cast<T2 * >(nullptr)分别将nullptr转换为T1和T2的指针类型,然后解引用求和,最后利用decltype推导出求和后的对象类型。由于编译器不会真的计算求值,因此这里求和操作不会有问题。
函数模板sum4则是利用了另外一个技巧,其实本质上与sum3相似。在标准库中提供了一个std::declval函数模板声明(没有具体实现),它将类型T转换成引用类型,这样在使用decltype推导表达式类型时不必经过构造函数检查。由于标准库中std::declval的实现比较复杂,因此我在这里实现了一个简化版本。declval<T1>() + declval<T2>()表达式分别通过declval将T1和T2转换为引用类型并且求和,最后通过decltype推导返回类型。
可以看出,虽然这两种方法都能达到函数返回类型后置的效果,但是它们在实现上更加复杂,同时要理解它们也必须有一定的模板元编程的知识。为了让代码更容易被其他人阅读和理解,还是建议使用函数返回类型后置的方法来推导返回类型。
本章介绍了C++11标准中的函数返回类型后置语法,通过这种方法可以让返回复杂类型的函数声明更加清晰易读。在无法使用C++14以及更新标准的情况下,通过返回类型后置语法来推导函数模板的返回类型无疑是最便捷的方法。