返回类型是函数签名的一部分吗?

在 C + + 中,返回类型是否被认为是函数签名的一部分?只修改返回类型不允许重载。

52031 次浏览

普通函数的签名中不包含返回类型。

(纸条: 我已经重写了这个答案,下面的评论不适用于这次修订-详情请参阅编辑历史)。

简介

然而,关于标准中的函数和函数声明的问题是复杂的。有两个层面需要考虑:

  • 声明
  • 实体

所谓的 函数声明可以声明函数实体或模板实体。如果声明了一个函数实体,那么您要么必须使用函数模板的显式专门化(指定所有参数) ,要么必须使用普通函数的声明。如果声明了一个模板实体,那么就是声明了一个主函数模板,或者是一个未指定某些参数的显式专门化。(这非常类似于“对象声明”与对象或引用的关系: 前者可以声明对象或引用。因此,对象声明可能不一定声明一个对象!).

标准定义函数的签名在 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/27/5
  • 什么是函数模板/专门化的签名: 14.5.5.1

作为参考,下面是最新的 C + + 0x 草案 n3000对 1.3.11中“签名”的描述,它对不同类型的实体的覆盖要完整得多:

函数的名称和参数类型列表(8.3.5) ,以及它是其成员的类或命名空间。如果函数或函数模板是一个类成员,它的签名还包括函数或函数模板本身上的 cv 限定符(如果有的话)和 ref 限定符(如果有的话)。函数模板的签名还包括它的返回类型和它的模板参数列表。函数模板专门化的签名包括它是其专门化的模板的签名及其模板参数(无论是显式指定的还是推导的)。[注意: 签名被用作名称混淆和链接的基础。ー尾注]

这取决于函数是否是 函数模板

C + + 模板——完整的指南中,Jusuttis 提供了 C + + 标准中给出的不同定义,但是具有相同的结果:

我们将函数的签名定义为以下信息:

  1. 函数的 不限定的名称
  2. 该名称的 同学们命名空间作用域,如果该名称具有内部链接,则为声明该名称的翻译单元
  3. 函数的 constvolatileconst volatile限定
  4. 函数参数的 类别
  5. 如果函数是从函数模板生成的,则返回 类型
  6. 如果函数是从函数模板生成的,则为 模板参数模板参数

正如 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))

它返回 double 4.0,而如果你想要一个 int 输出,你输入:

f(2, int(1))

返回 int 4。