在 C++ 中,返回类型是否被视为函数签名的一部分?并且只修改返回类型就不允许重载。
4 回答
普通函数在其签名中不包含返回类型。
(注意:我已经重写了这个答案,下面的评论不适用于这个修订版 - 有关详细信息,请参阅编辑历史)。
介绍
但是,标准中关于函数和函数声明的问题很复杂。有两层需要考虑:
- 声明
- 实体
所谓函数声明,可以声明一个函数实体,也可以声明一个模板实体。如果声明了一个函数实体,那么您要么必须处理函数模板的显式特化(指定所有参数),要么处理普通函数的声明。如果声明了模板实体,那么您声明的是主函数模板,或者未指定某些参数的显式特化。(这与“对象声明”与对象或引用的关系非常相似:前者可以声明对象或引用。所以对象声明不一定声明对象!)。
该标准定义了一个函数的签名以包括以下内容1.3.10
:
它的参数类型,如果函数是类成员,函数本身和声明成员函数的类的 cv 限定符(如果有)。函数模板特化的签名包括其模板参数的类型。(14.5.5.1)
它缺少此定义中的返回类型,这是函数模板特化(即声明作为模板特化的函数的函数声明)签名的一部分,正如14.5.5.1
(最近的 C++0x 工作论文修复了已经提到返回类型的问题1.3.10
):
函数模板特化的签名由函数模板的签名和实际模板参数(无论是显式指定还是推导)组成。
函数模板的签名由函数签名、返回类型和模板参数列表组成。
那么,签名究竟包含什么内容呢?
因此,当我们询问函数的签名时,我们必须给出两个答案:
- 对于函数模板的特化函数,签名包括返回类型。
- 对于不是特化的函数,返回类型不是签名的一部分。
但是请注意,在任何情况下,返回类型都是函数类型的重要组成部分。也就是说,以下内容无效:
void f();
int (*pf)() = &f; // different types!
如果只有返回类型不同,重载何时无效?
主要编译器目前拒绝以下代码:
int f();
double f(); // invalid
但接受以下代码:
template<typename T> int f();
template<typename T> double f(); // invalid?
但是,标准确实禁止仅在返回类型上有所不同的函数声明(定义重载何时有效,何时无效)。但是,它并没有准确定义“仅因返回类型而异”的含义。
标准段落参考:
- 什么时候可以重载函数声明:
13.1
- 什么是函数声明:
7/2
和7/5
- 函数模板/专业化的签名是什么:
14.5.5.1
作为参考,以下是最新的 C++0x 草案 n3000 中关于“签名”的说法1.3.11
,它在涵盖不同类型的实体方面更加完整:
函数的名称和参数类型列表 (8.3.5),以及它所属的类或命名空间。如果函数或函数模板是类成员,则其签名还包括函数或函数模板本身的 cv 限定符(如果有)和 ref 限定符(如果有)。函数模板的签名还包括其返回类型和模板参数列表。函数模板特化的签名包括作为特化的模板的签名及其模板参数(无论是显式指定的还是推导出的)。[ 注意:签名用作名称修改和链接的基础。——尾注]
这取决于函数是否是函数模板。
在C++ Templates -- the complete guides中,Jusuttis 提供了与 C++ 标准中给出的定义不同的定义,但结果相同:
我们将函数的签名定义为以下信息:
- 函数的非限定名称
- 该名称的类或命名空间范围,如果该名称具有内部链接,则声明该名称的翻译单元
- 函数的
const
,volatile
, 或const volatile
限定 - 函数参数的类型
- 它的返回类型,如果函数是从函数模板生成的
- 模板参数和模板参数,如果函数是从函数模板生成的
正如litb 所建议的,有必要澄清为什么返回类型是模板函数签名的一部分。
如果函数具有不同的签名,则它们可以在程序中共存。
. 也就是说,如果返回类型是模板参数:
template <typename T>
T foo(int a)
{return T();}
可以实例化两个仅在返回类型上有所不同的函数:
foo<int>(0);
foo<char>(0);
不仅:正如litb正确报道的那样,还可以重载两个模板函数,它们仅在返回类型上有所不同,即使返回类型不是从属名称。这是他的例子:
template<class T> int foo(T)
{}
template<class T> bool foo(T)
{}
// at the instantiation point it is necessary to specify the cast
// in order not to face ambiguous overload
((int(*)(char))foo<char>)('a');
它们足以成为类型的一部分,您可以基于仅返回类型不同的函数指针类型重载函数:
int IntFunc() { return 0; }
char CharFunc() { return 0; }
void FuncFunc(int(*func)()) { cout << "int\n"; }
void FuncFunc(char(*func)()) { cout << "char\n"; }
int main()
{
FuncFunc(&IntFunc); // calls void FuncFunc(int_func func)
FuncFunc(&CharFunc); // calls void FuncFunc(char_func func)
}
我发现隐式地使返回类型成为签名的一部分的有用方法是在输入中包含一个“虚拟”参数。
例如:
template <typename T>
T f(double x, T dummy) {
T output;
output = x * 2;
return output;
}
在这种情况下,如果您想要双输出,请输入:
f(2, double(1))
它返回双 4.0,而如果你想要一个 int 输出,你输入:
f(2, int(1))
它返回 int 4。