与函数指针、__cdecl 和模板混淆
Confusion with function pointer, __cdecl, and template
在Visual Studio2019年,我写了下面的测试代码,但是结果让我一头雾水
#include <iostream>
using namespace std;
template<class T, class Func>
int call(T x, Func f) { return f(x); }
int square(int x) { return x * x; }
int main() {
int (*func0) (int) = square; // line 0, OK
//int (func1)(int) = square; // line 1, wrong
int (__cdecl *func1) (int) = square; // line 2, OK
//int (__cdecl func2)(int) = square; // line 3, wrong
cout << ((int(__cdecl*)(int)) square)(5) << endl; // line 4, OK
//cout << ((int(__cdecl)(int)) square)(5) << endl; // line 5, wrong
cout << call<int, int (*)(int)>(5, square) << endl; // line 6, OK
//cout << call<int, int ()(int)>(5, square) << endl; // line 7, wrong
cout << call<int, int(__cdecl*)(int)>(5, square) << endl; // line 8, OK
cout << call<int, int(__cdecl)(int)>(5, square) << endl; // line 9, OK
return 0;
}
(我知道在使用 call
时可以省略类型,但这是一个实验。)
我以为我能够理解从第 0 行到第 7 行的所有内容。我的想法是 square
是一个函数指针,所以它的类型应该是 int (*) (int)
或者 int(__cdecl*) (int)
,这两个要么完全相同,要么可以相互转换(我没有改变项目的调用约定,所以默认是__cdecl
)。
然而,令我惊讶的是第 8 行和第 9 行都编译并且 运行 正确。为什么会这样?
通过比较第 6、7 行和第 8、9 行,我认为问题来自添加 __cdecl
,但在 Microsoft Docs 中没有提到这样的内容。
然后我打印出类型:
// ...
cout << typeid(square).name() << endl; // output: int __cdecl(int)
cout << typeid(*square).name() << endl; // output: int __cdecl(int)
cout << typeid(&square).name() << endl; // output: int(__cdecl*)(int)
cout << (typeid(square) == typeid(int(*) (int))) << endl; // output: false
cout << (typeid(square) == typeid(int(__cdecl) (int))) << endl; // output: true
cout << (typeid(square) == typeid(int(__cdecl*) (int))) << endl; // output: false
cout << (typeid(square) == typeid(*square)) << endl; // output: true
// ...
看来square
确实有类型int (__cdecl) (int)
。另外,我不明白为什么 square
和 *square
是同一类型...
谁能给我解释一下这些现象?
行错误:
int (func1)(int) = square; // line 1, wrong
是你少了一个'*',需要是:
int (*func1)(int) = square; // line 1, wrong
同
//int (__cdecl func2)(int) = square; // line 3, wrong
cout << ((int(__cdecl)(int)) square)(5) << endl; // line 5, wrong
需要
cout << ((int(__cdecl*)(int)) square)(5) << endl; // line 5, wrong
square
和 *square
是相同的类型,因为函数会衰减,就像数组一样,指向指针,除了(就像数组)在某些上下文中。特别是,衰减在 typeid
和 &
下被抑制,但在 *
下不被抑制,因此 typeid(square)
为您提供 square
、int (__cdecl)(int)
的类型,而 typeid(*square)
表示 typeid(*&square)
表示 typeid(square)
给出相同的东西。这导致了一个奇怪的事实,即你可以写任意多的 *
而它们什么都不做: *************square
与 square
.
相同
现在,对于您问题的其余部分,您写错了 "function taking int
returning int
" 类型。 int ()(int)
表示 "function taking no arguments returning function taking int
returning int
"。你想要 int(int)
。然后这有效:
cout << call<int, int(int)>(5, square) << "\n"; // line 7, fixed (FYI: endl is not normally necessary)
因为现在 call
有参数列表 (int x, int f(int))
,并且函数类型的参数声明自动调整为具有指向函数类型的指针,使得 call<int, int(int)>
在功能上等同于 call<int, int (*)(int)>
. (这不适用于变量声明或强制转换,因此第 1、3、5 行仍然不正确。)额外的括号导致类型被误解。第 9 行之所以有效,是因为将 __cdecl
放在括号内不会被误解(它们不是函数声明符,而是分组符号)。
cout << call<int, int (__cdecl)(int)>(5, square) << "\n"; // line 9, OK
再次调整call
的参数类型。 int (__cdecl f)(int)
变为 int (__cdecl *f)(int)
,这使得第 9 行在功能上与第 8 行相同。
在Visual Studio2019年,我写了下面的测试代码,但是结果让我一头雾水
#include <iostream>
using namespace std;
template<class T, class Func>
int call(T x, Func f) { return f(x); }
int square(int x) { return x * x; }
int main() {
int (*func0) (int) = square; // line 0, OK
//int (func1)(int) = square; // line 1, wrong
int (__cdecl *func1) (int) = square; // line 2, OK
//int (__cdecl func2)(int) = square; // line 3, wrong
cout << ((int(__cdecl*)(int)) square)(5) << endl; // line 4, OK
//cout << ((int(__cdecl)(int)) square)(5) << endl; // line 5, wrong
cout << call<int, int (*)(int)>(5, square) << endl; // line 6, OK
//cout << call<int, int ()(int)>(5, square) << endl; // line 7, wrong
cout << call<int, int(__cdecl*)(int)>(5, square) << endl; // line 8, OK
cout << call<int, int(__cdecl)(int)>(5, square) << endl; // line 9, OK
return 0;
}
(我知道在使用 call
时可以省略类型,但这是一个实验。)
我以为我能够理解从第 0 行到第 7 行的所有内容。我的想法是 square
是一个函数指针,所以它的类型应该是 int (*) (int)
或者 int(__cdecl*) (int)
,这两个要么完全相同,要么可以相互转换(我没有改变项目的调用约定,所以默认是__cdecl
)。
然而,令我惊讶的是第 8 行和第 9 行都编译并且 运行 正确。为什么会这样?
通过比较第 6、7 行和第 8、9 行,我认为问题来自添加 __cdecl
,但在 Microsoft Docs 中没有提到这样的内容。
然后我打印出类型:
// ...
cout << typeid(square).name() << endl; // output: int __cdecl(int)
cout << typeid(*square).name() << endl; // output: int __cdecl(int)
cout << typeid(&square).name() << endl; // output: int(__cdecl*)(int)
cout << (typeid(square) == typeid(int(*) (int))) << endl; // output: false
cout << (typeid(square) == typeid(int(__cdecl) (int))) << endl; // output: true
cout << (typeid(square) == typeid(int(__cdecl*) (int))) << endl; // output: false
cout << (typeid(square) == typeid(*square)) << endl; // output: true
// ...
看来square
确实有类型int (__cdecl) (int)
。另外,我不明白为什么 square
和 *square
是同一类型...
谁能给我解释一下这些现象?
行错误:
int (func1)(int) = square; // line 1, wrong
是你少了一个'*',需要是:
int (*func1)(int) = square; // line 1, wrong
同
//int (__cdecl func2)(int) = square; // line 3, wrong
cout << ((int(__cdecl)(int)) square)(5) << endl; // line 5, wrong
需要
cout << ((int(__cdecl*)(int)) square)(5) << endl; // line 5, wrong
square
和 *square
是相同的类型,因为函数会衰减,就像数组一样,指向指针,除了(就像数组)在某些上下文中。特别是,衰减在 typeid
和 &
下被抑制,但在 *
下不被抑制,因此 typeid(square)
为您提供 square
、int (__cdecl)(int)
的类型,而 typeid(*square)
表示 typeid(*&square)
表示 typeid(square)
给出相同的东西。这导致了一个奇怪的事实,即你可以写任意多的 *
而它们什么都不做: *************square
与 square
.
现在,对于您问题的其余部分,您写错了 "function taking int
returning int
" 类型。 int ()(int)
表示 "function taking no arguments returning function taking int
returning int
"。你想要 int(int)
。然后这有效:
cout << call<int, int(int)>(5, square) << "\n"; // line 7, fixed (FYI: endl is not normally necessary)
因为现在 call
有参数列表 (int x, int f(int))
,并且函数类型的参数声明自动调整为具有指向函数类型的指针,使得 call<int, int(int)>
在功能上等同于 call<int, int (*)(int)>
. (这不适用于变量声明或强制转换,因此第 1、3、5 行仍然不正确。)额外的括号导致类型被误解。第 9 行之所以有效,是因为将 __cdecl
放在括号内不会被误解(它们不是函数声明符,而是分组符号)。
cout << call<int, int (__cdecl)(int)>(5, square) << "\n"; // line 9, OK
再次调整call
的参数类型。 int (__cdecl f)(int)
变为 int (__cdecl *f)(int)
,这使得第 9 行在功能上与第 8 行相同。