10-9 函数的类型推断
考虑以下程序:
int add(int x, int y)
{
return x + y;
}
当编译此函数时,编译器将求值 x + y 的结果为 int 类型,并确保返回值类型与函数声明的返回类型匹配(或返回值类型可转换为声明的返回类型)。
使用auto进行返回类型推导
由于编译器本就需要从 return 语句推导返回类型(以确保值可转换为函数声明的返回类型),C++14 扩展了 auto 关键字以实现函数返回类型推导。具体实现是将 auto 关键字替换函数声明中的返回类型。
例如:
auto add(int x, int y)
{
return x + y;
}
由于返回语句返回的是 int 值,编译器将推断该函数的返回类型为 int。
使用 auto 返回类型时,函数内所有返回语句必须返回相同类型的值,否则将导致错误。例如:
auto someFcn(bool b)
{
if (b)
return 5; // return type int
else
return 6.7; // return type double
}

在上例函数中,两个返回语句返回不同类型的值,因此编译器将报错。
若因特殊需求需要此类情况,可通过两种方式解决:明确指定函数返回类型(此时编译器会尝试将所有不匹配的返回表达式隐式转换为显式类型),或显式将所有返回语句转换为相同类型。在上例中,后者可通过将5改为5.0实现,但对于非字面量类型也可使用static_cast。
返回类型推导的优势
返回类型推导的最大优势在于:由编译器推导函数返回类型可消除类型不匹配风险(避免意外转换)。
当函数返回类型易变(即实现变更可能导致返回类型改变)时,此特性尤为重要。此时显式声明返回类型意味着:实现发生影响性变更时需更新所有相关返回类型。若运气好,编译器会在相关返回类型未更新时报错;若运气差,则会出现非预期的隐式转换。
在其他情况下,函数的返回类型可能冗长复杂,或不够直观。此时可使用auto简化:
// let compiler determine the return type of unsigned short + char
auto add(unsigned short x, char y)
{
return x + y;
}

我们在第11.8节——带有多个模板类型的函数模板中,将进一步探讨此类情况(以及如何表达此类函数的实际返回类型)。
返回类型推导的弊端
返回类型推导存在两大主要缺陷:
- 使用 auto 返回类型的函数必须在使用前完成完整定义(仅前向声明不足以生效)。例如:
#include <iostream>
auto foo();
int main()
{
std::cout << foo() << '\n'; // the compiler has only seen a forward declaration at this point
return 0;
}
auto foo()
{
return 5;
}
在作者的机器上,这会导致以下编译错误:

这很合理:前向声明提供的信息不足以让编译器推断函数的返回类型。这意味着返回 auto 类型的普通函数通常只能在其定义文件内部调用。
- 当对象使用类型推断时,初始化器始终作为同一语句的一部分存在,因此通常不难确定将推断出的类型。但函数类型推断则不同——函数原型无法揭示其实际返回类型。优秀的编程IDE应明确显示函数的推断类型,若缺乏此功能,用户就必须深入函数体内部才能确定返回类型,这大大增加了出错概率。通常我们倾向于明确声明接口中的类型(函数声明即接口)。
与对象类型推导不同,函数返回类型推导的最佳实践尚未形成共识。我们建议普遍避免使用函数返回类型推导。
最佳实践
优先采用显式返回类型而非类型推导(除非返回类型无关紧要、难以表达或易受影响)。
尾随返回类型语法
auto 关键字也可用于采用尾随返回语法trailing return syntax声明函数,即在函数原型的其余部分之后指定返回类型。
请看以下函数示例:
int add(int x, int y)
{
return (x + y);
}
使用尾部返回语法,这段代码可等效写为:
auto add(int x, int y) -> int
{
return (x + y);
}
在此情况下,auto 不会执行类型推导——它只是使用尾随返回类型的语法组成部分。
为何要使用这种写法?原因如下:
- 对于具有复杂返回类型的函数,尾随返回类型能提升函数的可读性:
#include <type_traits> // for std::common_type
std::common_type_t<int, double> compare(int, double); // harder to read (where is the name of the function in this mess?)
auto compare(int, double) -> std::common_type_t<int, double>; // easier to read (we don't have to read the return type unless we care)
- 尾随返回类型语法可用于对齐函数名称,使连续的函数声明更易于阅读:
auto add(int x, int y) -> int;
auto divide(double x, double y) -> double;
auto printSomething() -> void;
auto generateSubstring(const std::string &s, int start, int len) -> std::string;
对于进阶读者
3. 如果我们有一个函数,其返回类型必须根据函数形参的类型进行推断,那么普通的返回类型将无法满足需求,因为编译器在该位置尚未看到形参。#include <type_traits> // note: decltype(x) evaluates to the type of x std::common_type_t<decltype(x), decltype(y)> add(int x, double y); // Compile error: compiler hasn't seen definitions of x and y yet auto add(int x, double y) -> std::common_type_t<decltype(x), decltype(y)>; // ok
- 尾随返回语法在C++某些高级特性中也是必需的,例如lambda表达式(详见第20.6课——lambda表达式(匿名函数)入门)。
目前我们建议继续使用传统函数返回语法,除非遇到必须使用尾随返回语法的情况。
函数形参类型无法使用类型推断
许多刚接触类型推导的新手程序员会尝试类似这样的代码:
#include <iostream>
void addAndPrint(auto x, auto y)
{
std::cout << x + y << '\n';
}
int main()
{
addAndPrint(2, 3); // case 1: call addAndPrint with int parameters
addAndPrint(4.5, 6.7); // case 2: call addAndPrint with double parameters
return 0;
}

遗憾的是,类型推导不适用于函数参数。在C++20之前,上述程序无法编译(会报错提示函数参数不能使用auto类型)。
在C++20中,auto关键字得到了扩展,使得上述程序能够编译并正常运行——但此时auto并未触发类型推导,而是激活了名为函数模板的特性,该特性正是为处理此类情况而设计。
相关内容
我们在第11.6节——函数模板中介绍了函数模板,并在第11.8节——带有多个模板类型的函数模板中讨论了函数模板中auto的使用。

浙公网安备 33010602011771号