Путаница с указателем на функцию, __cdecl и шаблоном - PullRequest
2 голосов
/ 18 июня 2020

В Visual Studio 2019 я написал следующие тестовые коды, но результаты меня запутали.

#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 одного типа ...

Может ли кто-нибудь объяснить мне эти явления?

Ответы [ 2 ]

2 голосов
/ 18 июня 2020

square и *square относятся к одному типу, потому что функции распадаются, как и массивы, на указатели, за исключением (точно так же, как массивы) в определенных контекстах. В частности, распад подавляется под typeid и &, но не под *, поэтому typeid(square) дает вам тип square, int (__cdecl)(int), а typeid(*square) означает typeid(*&square) означает typeid(square) дает то же самое. Это приводит к тому странному факту, что вы можете написать столько * s, сколько захотите, и все они ничего не сделают: *************square это то же самое, что square.

Теперь для остальной части вашего вопрос, вы неправильно написали тип "функция принимает int возвращает int". int ()(int) означает «функция без аргументов, возвращающая функцию, принимающую int, возвращающую 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.

0 голосов
/ 18 июня 2020

Ошибка со строкой:

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