【问题标题】:Why does casting a function to a function type that is identical except for return type fail? [duplicate]为什么将函数强制转换为除返回类型外相同的函数类型会失败? [复制]
【发布时间】:2012-12-04 00:44:35
【问题描述】:

可能重复:
Is the return type part of the function signature?

跟进一个相关但切题的问题 (How to disambiguate function templates that differ only by return type?),我想问一个与函数的 返回类型 不被认为是函数的签名。

考虑以下代码:

#include <iostream>

int foo()
{
    return 0;
}

int main()
{
    long n = static_cast<long(&)()>(foo)(); // Error: incorrect return type
    int p = static_cast<int(&)()>(foo)(); // Compiles just fine 
}

上述代码行导致编译错误,因为 foo 被强制转换为的函数类型的 return 类型与 return 不匹配函数类型foo

但我认为函数的返回类型对函数的签名没有影响!

按照某种思路,既然函数签名long(&amp;)()foo的签名匹配,那么foo对这种类型的函数的强制转换应该会成功。

但是,转换没有成功。推理哪里出错了?如果转换由于函数签名不能失败,那么为什么转换失败?

【问题讨论】:

  • @AndreasBrinck 我已经知道返回类型不是函数签名的一部分。这不是我要问的。
  • 您假设转换为函数引用类型只需要相同的签名。这种假设是不正确的。它需要相同的参数和返回类型(换句话说,相同的函数类型)。
  • @Dan 如果您阅读我提到的答案,您会发现它明确回答了您的问题!
  • @AndreasBrinck 我认为“重复的问题”意味着问题本身就是重复的。我不正确吗?如果答案出现在不同问题的答案中,问题是否重复?
  • @Dan 如果另一个问题的答案在这里同样适用,那就是重复了。在链接的问题中就是这种情况(尽管可能以没有回答您的问题的方式回答了另一个问题)。

标签: c++


【解决方案1】:

你是正确的,返回类型不构成函数的签名的一部分。

但是,它确实构成函数的类型的一部分,并且不允许将指针转换为不兼容的函数类型。

【讨论】:

  • 漂亮、简洁的答案。我希望我能接受多个答案。
  • 阅读者注意:有关其他有用的相关信息,请参阅 David 的回答。
【解决方案2】:

返回类型类型的一部分,虽然不用于重载解析。重要的是不要混淆这些术语。基本上,类型包括参数和返回值,但在重载决策期间,不考虑返回类型。函数或函数指针的类型是调用者和被调用者之间的约定,他们必须完全同意条款。

从实际的角度考虑,如果您的建议被允许,会发生什么。想象一个调用约定,其中调用者保留空间并将指向该空间的指针传递给函数,然后函数将在该位置构造返回的对象(这实际上是一个非常常见的调用约定)。现在考虑您被允许执行您建议的演员表和以下用例

static_assert(sizeof(T1)<sizeof(T2));
T2 f();
T1 (*p)() = &f;

p();                                  // call

现在,当编译器处理p() 时,它会在某处保留空间,并给出需要保留sizeof(T1) 的函数类型。然后它调用该函数,该函数最终调用f,它将sizeof(T2) 字节写入导致溢出的位置。

即使尺寸匹配,代码也会有问题。在sizeof(int)==sizeof(float) 所在的平台中考虑T1==intT2==float。虽然上面的代码不会导致缓冲区溢出,但存储在返回类型位置的位模式将是 float 的位模式,而不是 int 的位模式。

【讨论】:

  • 这个答案和大卫一起去。我希望我能同时接受。
【解决方案3】:

函数的返回类型不是签名的一部分,但签名不是编译器为了正确调用函数指针或引用所引用的函数而需要知道的。

除了参数之外,编译器还需要知道返回类型,以便它可以为堆栈上的返回值腾出空间,或者从正确的寄存器中读取返回值,或者任何调用约定给定实现中的需求。

这就是为什么返回类型是函数类型的一部分——以便类型告诉编译器在编译时它需要知道什么,以便发出代码来调用函数。

函数签名long(&amp;)()匹配foo的签名

long(&amp;)() 不是函数签名,它是一个类型foo(void) 是一个(表示一个)函数签名。它包括名称和参数。但是您永远不需要在 C++ 代码中指定函数签名(嗯,也许作为传递给 dlsym 或类似的字符串)。函数签名的明确表示是给定实现中函数的错位名称。修改方案不是标准的,它取决于实现(尽管如果不同的实现想要调用彼此的库,那么它们必须使用相同的方案,因此操作系统可能会指定一个)。

【讨论】:

  • 谢谢。这个答案以及 Mike 和 David 的答案都很出色。
  • 存在一个问题,即是否保证损坏的名称与函数签名相对应 - 这似乎不能保证。
  • @DanNissenbaum:它不会立即跟进,但 C++11 确实说签名用于链接。但是,损坏的名称可以包含不属于签名的内容。最明显的是指定要使用的调用约定,在 Win32 等具有多个的系统上。这超出了标准的范围,因此没有将其作为签名定义的一部分提及。不过,我所说的“明确表示”的意思是 foo(void) 不是正式的语法。损坏的 C++ 名称确实指定了签名,并且可能指定了其他内容。
  • 似乎函数type和函数signature都用于链接。我很好奇在哪种情况下 signature 在链接阶段专门使用(而不是类型),在这种情况下 type 在链接阶段专门使用(与签名相反)。这与我刚刚发布的另一个问题有关,stackoverflow.com/questions/13687607/…
【解决方案4】:

函数的返回类型视为其签名(及其类型)的一部分。但是,如果返回类型是“协变的”,则允许将函数指针分配给具有不同返回类型的变量。

【讨论】:

  • "函数的返回类型被认为是其签名的一部分" -- 对于常规的非模板函数,它不是(如果您怀疑,请在 C++ 标准中查找 [defns.signature]它),但它是否是签名的一部分根本不相关。
  • 你确定吗?我几乎可以肯定,非模板函数的返回类型 not 是其签名的一部分。例如,请参阅问题下方 Andreas Brinck 的评论中链接的问题 (stackoverflow.com/questions/290038/…)。
猜你喜欢
  • 1970-01-01
  • 2010-12-22
  • 2015-12-13
  • 1970-01-01
  • 1970-01-01
  • 2018-11-23
  • 2020-02-04
  • 2011-05-18
相关资源
最近更新 更多