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?
square
and *square
are the same type because functions decay, just like arrays do, to pointers, except (just like arrays) under certain contexts. In particular, decay is suppressed under typeid
and &
, but not under *
, so typeid(square)
gives you the type of square
, int (__cdecl)(int)
, while typeid(*square)
means typeid(*&square)
means typeid(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: *************square
is the same as square
.
Now, to the rest of your question, you wrote the type "function taking int
returning int
" wrong. int ()(int)
means "function taking no arguments returning function taking int
returning int
". You wanted int(int)
. Then this works:
cout << call<int, int(int)>(5, square) << "\n"; // line 7, fixed (FYI: endl is not normally necessary)
Because now call
has arguments list (int x, int f(int))
, and a parameter declaration of function type is automatically adjusted to have pointer to function type, making call<int, int(int)>
functionally identical to call<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 __cdecl
inside the parentheses makes them not be misinterpreted (instead of being the function declarator, they become grouping symbols).
cout << call<int, int (__cdecl)(int)>(5, square) << "\n"; // line 9, OK
Again, the type of call
's parameter is adjusted. int (__cdecl f)(int)
becomes int (__cdecl *f)(int)
, which makes line 9 functionally identical to line 8.