Search code examples
c++visual-studiotemplatesfunction-pointerscdecl

Confusion with function pointer, __cdecl, and template


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?


Solution

  • 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.