概述
对于传统的C和C++编程语言中,函数的声明方式为:
<返回类型> <函数标识符> ( <形式参数列表> )
而在C++11标准(ISO/IEC 14882:2011)起,由于引入了现代化编程语言中常用的类型自动推导,因此为了能使该语法体系更为完备,从而让函数定义可以通过 形参类型 或 根据函数体中的返回语句直接推导出该函数的返回类型。所以引出了新型的函数声明方式:
auto <函数标识符> ( <形式参数列表> ) -> <返回类型>
如果当前定义的函数可直接通过其函数体中的返回语句直接推导出其返回类型,那么后面的 “ -> <返回类型> ” 可省。
下面就来针对上述两种情况分别进行举例:
#include <cstdio>
#include <typeinfo>
// 根据形参类型来推导其返回类型
template <typename S, typename T>
static auto Test1(const S& s, const T& t) -> decltype(s + t)
{
return s - t;
}
// 根据返回表达式类型来直接推导其返回类型。
// 注意!这里没有显式声明Test2函数的返回类型。
static auto Test2(float f)
{
auto const value = Test1<int, unsigned>(100, 20U);
// MSVC++下输出:value type: unsigned int, value = 80
printf("value type: %s, value = %u\n", typeid(value).name(), value);
return f + 1.0f;
}
对于第一个Test1函数的声明中,由于在 -> 符号之前,形参s和t对编译器都已经可见,因此可在后续直接对它们进行引用,从而可通过 decltype(s + t) 做类型推导。而对于第二个Test2函数的声明中,我们可以看到这里没有 -> 以及后续的类型说明,而直接跟函数体。因为这里编译器可直接根据该函数的返回语句直接获得该函数的返回类型。
因此,如果我们用Visual Studio 2019或更新版本的话,直接将鼠标光标移动到Test2标识符上,就会自动出现对该函数的完整类型说明,如下图所示。

特点
玩过Rust或Swift编程语言的朋友们想必对C++11所引入的新型函数声明方式感到很熟悉了吧。其实从语言表达力上而言,这种函数声明方式可更容易地与函数调用表达式予以区分,而这对于C++语言而言尤为有用。下面我们就来看些例子:
#include <cstdio>
#include <functional>
extern "C" auto CPPTest()
{
decltype(int()) const a = int();
const std::function<int()>& fun = [a]() {
return a + 1;
};
// 输出:value = 1
printf("value = %d\n", fun());
}
对于上述代码,笔者估计会让很多C++初学者望而生畏……那我们先来一条条分析。
我们先看第一条语句中的 decltype(int()) 里的 int() 表示啥?要解释这个问题之前,我们必须要了解——C++11中的 decltype() 里必须是一个表达式而不能是一个类型名!所以这里的 int() 其实是利用 int 类型构造一个其默认值的对象,换句话说就是构造一个值为 0 的 int 类型对象,使得 decltype(int()) 可直接推导出 int 类型。此外,该语句中 = 右边的 int() 表达式也是如此。所以整条语句就相当于:int const a = 0;
然后再看第二条语句,这里的 std::function<int()> 中的 int() 就表示函数类型了。因为根据 std::function 模板形参的定义可知,这里需要传递的是一个表征该函数对象的函数类型,因此这里指明的是返回类型为 int 且不带任何形参的函数类型。
所以我们在使用C++编程语言时会时常碰到类似的代码。因此笔者之前有一个良好的习惯就是对于函数声明,倘若该函数没有任何形式参数,那么直接用 void 填充。这么一来函数声明与函数调用就能很好地区分开了。我们可以用此方式改写一下上述代码中的第二条语句:
const std::function<int(void)>& fun = [a]() {
return a + 1;
};
是不是感觉好多了?
但是,一些现代IDE工具,尤其是使用Clang语法检查工具的IDE,诸如CLion以及Android Studio,对于C++语言(注意,这里只针对C++,而不是C语言。C语言是完全合法且必要的。)中在形参列表里使用 void 的地方都会报出警告提示:形参列表不需要使用void,建议移除。为了迎合这些语法检测工具的口味,笔者当前也更偏好于使用新型方式来声明函数类型。
使用新型方式声明函数类型
以上提到的都是新型的函数声明方式。那对于函数类型本身而言呢,我们当然也可以使用这种声明方式。因此我们可以将上述代码的第二条语句写成以下形式:
const std::function<auto () -> int> &fun = [a]() {
return a + 1;
};
各位可以看到,跟Rust、Swift非常相似吧~而这种形式显然可以与函数调用完全区分开了。
笔者下面将例举一些更高级的例子供大家参考,从而可以激发起各位的Hack及Geek精神~
#include <cstdio>
#include <functional>
// 函数Foo2的类型为:auto () -> int
static auto Foo2() { return 10; }
// 函数Foo的老旧声明表达为:
// int (*Foo(int a)) (void)
static auto Foo(int a) -> auto (*)() -> int
{
printf("a is %d in Foo!\n", a);
return &Foo2;
}
extern "C" auto CPPTest()
{
// 函数类型的别名定义
typedef auto func_type() -> int;
using ft = auto (void) -> int;
auto (*pFunc)(int) -> auto (*)() -> int = &Foo;
// 输出:a is 20 in Foo!
auto a = pFunc(20)();
// 输出:Finally, a = 10
printf("Finally, a = %d\n", a);
struct S
{
auto method(double d)
{
return float(d - 1.0);
}
} s;
auto (S::* pMethod)(double) -> float = &S::method;
auto const f = (s.*pMethod)(2.5);
// 输出:f = 1.500000
printf("f = %f\n", f);
}
通过上述代码,我们可以脑补到关于函数指针类型的新型声明方法:
auto (* <函数指针标识符>) ( <形参列表> ) -> <返回类型>
而指向某个类类型成员方法的指针的声明方法为:
auto (<类型名> :: * <指针标识符> )( <形参列表> ) -> <返回类型>
各位觉得是不是颇有意思?如果还有其他新奇的想法或是有任何疑问,欢迎在底下留言~