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