In Visual Studio 2019, I have written the following test codes, but the results confused me.
#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;
}
(I am aware that I can omit the types when using call, but this is an experiment.)
I thought I was able to understand everything from line 0 to line 7. What I had in mind is that square is a funtion pointer, so it should have type int (*) (int) or perhaps int(__cdecl*) (int), and these two are either identical or can be casted to each other (I didn't change the calling convention of the project, so the default is __cdecl).
However, I was surprised that both line 8 and line 9 compile and run correctly. Why does this happen?
By comparing lines 6, 7 with lines 8, 9, I think the problem comes from adding __cdecl, but in Microsoft Docs nothing like this is mentioned.
I then printed out the types:
// ...
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
// ...
It seems that square indeed has type int (__cdecl) (int). Also, I don't understand why square and *square are of the same type...
Could someone explain these phenomena to me?
squareand*squareare the same type because functions decay, just like arrays do, to pointers, except (just like arrays) under certain contexts. In particular, decay is suppressed undertypeidand&, but not under*, sotypeid(square)gives you the type ofsquare,int (__cdecl)(int), whiletypeid(*square)meanstypeid(*&square)meanstypeid(square)gives the same thing. This leads to the odd fact that you can write as many*s as you want and they will all do nothing:*************squareis the same assquare.Now, to the rest of your question, you wrote the type "function taking
intreturningint" wrong.int ()(int)means "function taking no arguments returning function takingintreturningint". You wantedint(int). Then this works:Because now
callhas arguments list(int x, int f(int)), and a parameter declaration of function type is automatically adjusted to have pointer to function type, makingcall<int, int(int)>functionally identical tocall<int, int (*)(int)>. (This does not work for variable declarations or casts, so lines 1, 3, 5 remain incorrect.) The extra parentheses caused the type to be misinterpreted. Line 9 works because putting__cdeclinside the parentheses makes them not be misinterpreted (instead of being the function declarator, they become grouping symbols).Again, the type of
call's parameter is adjusted.int (__cdecl f)(int)becomesint (__cdecl *f)(int), which makes line 9 functionally identical to line 8.