C++ 返回类型上的 const 是否无效?为何编译器行为不同?
结论
按照 C++ 类型规则,原例中 f<int> 的返回类型是 const int。因此,const int(*x)(int) = f; 应当成立,int(*y)(int) = f; 应当被拒绝。返回类型是函数类型的一部分,匹配函数指针时也要考虑它。标准草案:函数类型、标准草案:获取重载函数地址
题述 GCC、Clang 和 EDG 的接受与拒绝行为符合这些规则。Clang 和 EDG 发出警告,并不意味着这两种函数指针类型相同。题述 MSVC 的处理存在标准符合性问题;由于没有给出版本和编译选项,这一判断只针对题中描述的表现。
为什么 const 保留在返回类型中
原代码使用了 C++20 的简写函数模板语法:
const auto f(auto) { return 1; }
可以将它理解为以下形式:
template<class T>
const auto f(T) { return 1; }
根据 return 1;,返回类型中的 auto 推导为 int。虽然返回的是整数,声明中显式写出的 const 仍会保留,因此完整返回类型是 const int。标准草案:占位类型推导
取 f<int> 的地址,得到的指针类型是:
const int (*)(int)
它与 int (*)(int) 是不同的类型,也不能通过普通隐式转换去掉函数返回类型中的 const。
为什么警告说 const 没有作用
函数的返回类型和调用表达式的类型需要分开看。
对于非类、非数组类型的 prvalue,标准要求在进一步分析表达式之前移除其顶层 cv 限定。因此,f<int> 的返回类型虽然是 const int,调用表达式 f(0) 的类型却是 int。标准草案:表达式类型调整
以下断言给出了这两条规则的预期结果:
#include <type_traits>
const auto f(auto) { return 1; }
// 函数指针保留返回类型中的 const。
static_assert(std::is_same_v<
decltype(&f<int>),
const int (*)(int)
>);
// 调用产生的标量 prvalue 不保留顶层 const。
static_assert(std::is_same_v<decltype(f(0)), int>);
// 两种函数指针类型不同。
static_assert(!std::is_same_v<
const int (*)(int),
int (*)(int)
>);
警告的意思是,对于普通整数的调用结果,这个 const 没有提供额外的只读保护。不能由此推断它不参与函数类型。
实际代码应该怎样写
如果函数只是按值返回整数,通常可以直接去掉返回类型上的 const:
auto f(auto) { return 1; }
int (*p)(int) = f;
如果需要保留原有函数声明,函数指针就应与它的类型匹配:
const auto f(auto) { return 1; }
const int (*p)(int) = f;
如果外部接口固定要求 int (*)(int),可以增加一个返回 int 的适配函数。无捕获 lambda 也可以完成适配:
const auto f(auto) { return 1; }
int (*callback)(int) = +[](int value) -> int {
return f(value);
};
这样创建了一个返回类型匹配的新函数入口。不要通过强制转换函数指针来掩盖类型差异。
注意适用范围
上述对调用表达式去除顶层 const 的解释适用于这里的标量按值返回。返回 const T&、返回 const T*,以及按值返回带 const 的类对象,都需要分别分析,不能直接套用整数示例的结论。
另外,形成函数类型时会移除按值参数的顶层 cv 限定。这是针对参数的规则,不能用来解释或删除返回类型上的 const。标准草案:函数参数类型调整
备注:内容仅供参考。
