Подтвердить что ты не робот

Функция-функция-указ "распад"

Как мы знаем, параметр, который выглядит как void(), будет переписан как void(*)(). Это похоже на разложение матрицы на указатель, где int[] становится int*. Существует много случаев, когда использование массива будет разлагать его на указатель. Существуют ли случаи, отличные от параметров, где функции "распадаются"?

Стандарт С++ гласит:

§8.3.5/5

... После определения типа каждого параметра любой параметр тип "массив Т" или "возвращающая функцию Т" настраивается как "указатель to T" или "указатель на функцию возврата T", соответственно...

Поскольку комментатор ниже, похоже, мне не верит. Где мой компилятор показывает.

void handler(void func())
{
    func(42);
}

main.cpp: In function 'void handler(void (*)())':
main.cpp:5:12: error: too many arguments to function
     func(42);
        ^
4b9b3361

Ответ 1

Существует три преобразования, которые считаются преобразованиями lvalue: lvalue-to-rvalue, array-to-pointer и функцией-to-pointer. Вы можете вызвать этот "распад", так как это будет std::decay для этих типов, но стандарт просто вызывает это преобразование от функции к указателю [conv.func]:

Значение функции типа T может быть преобразовано в prvalue типа "указатель на T". Результатом является указатель на функцию.

Если вы спрашиваете, к чему относятся случаи, когда происходит преобразование функции в указатель, они в основном такие же, как и при других двух преобразованиях lvalue. Если мы просто переходим к стандарту по порядку, то следующий список - это исчерпывающий список случаев, когда происходит преобразование функции в указатель:

Используя функцию в качестве операнда, [expr]/9:

Всякий раз, когда выражение glvalue появляется как операнд оператора, который ожидает prvalue для этого операнда, применяются стандартные значения lvalue-to-rvalue (4.1), стандартные преобразования (4.2) или функциональные переменные (4.3) для преобразования выражения в prvalue.

Использование функции в качестве аргумента функции varargs, [expr.call]/7:

Если для данного аргумента нет параметра, аргумент передается таким образом, что получающая функция может получить значение аргумента, вызвав va_arg (18.10)... Значение lvalue-to-rval ( 4.1), переменные-указатели (4.2) и стандартные-преобразования (4.2) выполняются по выражению аргумента.

Вы можете static_cast удалить это преобразование, [expr.static.cast]/7:

Обратный к любой стандартной последовательности преобразования (раздел 4), не содержащей значения lvalue-to-rvalue (4.1), arrayto- указатель (4.2), функция-to-pointer (4.3), нулевой указатель (4.10), указатель на нулевой элемент (4.11) или логический (4.12) преобразование можно выполнить явно с помощью static_cast.

Хотя в противном случае операнд, в который вы проходите, будет преобразован, [expr.static.cast]/8:

Преобразования lvalue-to-rvalue (4.1), array-to-pointer (4.2) и function-to-pointer (4.3) применяются к операнд.

Используя reinterpret_cast, [expr.reinterpret.cast]/1:

Результат выражения reinterpret_cast<T>(v) является результатом преобразования выражения v в тип T. Если T является ссылочным типом lvalue или ссылкой rvalue на тип функции, результатом является lvalue; если T является rvalue ссылается на тип объекта, результатом является значение xvalue; в противном случае результатом будет prvalue и lvalue-torvalue (4.1), преобразование от массива к указателю (4.2) и стандартное преобразование с помощью функции-to-pointer (4.3) выполняется на выражение v.

Используя const_cast, [expr.const.cast], в основном идентичной формулировке выше. Используя условный оператор, [expr.cond]:

Выполняются стандартные значения преобразования Lvalue-to-rvalue (4.1), преобразования по методу "массив-в-указатель" (4.2) и стандартного преобразования функции-to-pointer (4.3) на втором и третьем операндах.

Обратите внимание, что во всех вышеперечисленных случаях это всегда все преобразования lvalue.

Преобразования между функциями и указателями также возникают в шаблонах. Передача функции как непигового параметра, [temp.arg.nontype]/5.4:

Для шаблона-типа типа типа указателя типа для функции преобразование функции в указатель (4.3) применяется

Или тип вывода, [temp.deduct.call]/2:

Если P не является ссылочным типом:

  • - Если A является типом массива, тип указателя, созданный стандартным преобразованием (4.2) с использованием массива к указателю, вместо A используется для вывода типа; в противном случае,
  • - Если A - это тип функции, тип указателя, созданный стандартным преобразованием функции-to-pointer (4.3) вместо A используется для вывода типа; в противном случае,

Или вывод шаблона функции преобразования, примерно с той же формулировкой.

И, наконец, конечно, std::decay сам, определенный в [meta.trans.other], акцент мой:

Пусть U be remove_reference_t<T>. Если is_array<U>::value истинно, член typedef type должен быть равен remove_extent_t<U>*. Если значение is_function<U>::value истинно, тип typedef-члена равен add_pointer_t<U>, В противном случае тип typedef члена равен remove_cv_t<U>. [Примечание. Это поведение похоже на lvalue-to-rvalue (4.1), преобразования от массива к указателю (4.2) и преобразования функции-to-pointer (4.3), когда выражение lvalue используется как rvalue, но также линяет cv-квалификаторы из типов классов в порядке для более точной модели по стоимости аргумент передача. -end note]

Ответ 2

Когда речь идет о типах данных, функции не являются гражданами первого класса в C и С++ (вопрос о С++, но поведение наследуется от C). Это код, а не данные, и они не могут быть скопированы, переданы в качестве аргументов в функции или возвращены функциями (но все это может случиться с указателями на функции.)

Вот почему имя функции обрабатывается как указатель на эту функцию, а не как тело функции.

Возможность использовать функцию (имя) вместо указателя на функцию - это просто вежливость, которую язык делает программисту, а не "распад".

То же самое для массивов: они не копируются, не передаются как аргументы функции и не возвращаются функциями. Вместо них используется адрес их первого элемента (скопированный, переданный как аргумент функции или возвращаемый функциями). Вот почему имя массива можно использовать вместо адреса его первого элемента и снова, это всего лишь способ записать меньше (и менее обфускации) кода.

Для компилятора функция представляет собой блок памяти (который содержит код, который должен быть выполнен в какое-то время), который не перемещается и идентифицируется по его адресу, то есть указателю на функцию. Массив также представляет собой блок данных который не перемещается и идентифицируется по его адресу (который также является адресом его первого элемента). Опять же, это указатель.

Понятия function и array на более высоких уровнях (C, С++) преобразуются компилятором в примитивные значения (указатели), которые понимаются нижними уровнями (ассемблер, машинный код).

Ответ 3

Еще одно очевидное сходство между массивами и функциями будет следующим:

void bar(string message) 
{
    cout << message << endl;
}

void main()
{
    int myArray[10];
    int* p = myArray; //array to pointer to array

    void (*f)(string);
    f = bar; //function to function pointer decay
}

Ответ 4

да, они оба указатели, сначала указатель функции, второй - указатель на блок int. * выглядит как точка.