18

As we know a parameter that looks like void() will be rewritten as void(*)(). This is similar to array-to-pointer decay where int[] becomes int*. There are many cases where using an array will decay it to a pointer. Are there cases other than parameters where functions "decay"?

The C++ standard states:

§8.3.5/5

... After determining the type of each parameter, any parameter of type “array of T” or “function returning T” is adjusted to be “pointer to T” or “pointer to function returning T,” respectively...

Since the commenter below doesn't seem to believe me..here's what my compiler shows.

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);
        ^
user4179986
  • 205
  • 2
  • 4
  • @Ed Care to explain? – user4179986 Oct 25 '14 at 06:06
  • What is the purpose of this question – Ed Heal Oct 25 '14 at 06:07
  • 1
    What does 'looks like' mean' and 'will be' mean. Who does the rewriting? – Ed Heal Oct 25 '14 at 06:08
  • 2
    @Ed To get information on a language feature that I was previously not aware of? – user4179986 Oct 25 '14 at 06:08
  • @Ed I've updated the question with a standard quote. I'm not sure what you want me to say. – user4179986 Oct 25 '14 at 06:13
  • What does look like mean? How can they degrade from one data type to the other. Besides in C++ avoid `void` except as a return type – Ed Heal Oct 25 '14 at 06:18
  • @Ed I really don't get your point. `void func()` becomes `void (*func)()`. – user4179986 Oct 25 '14 at 06:21
  • Not as a type for a parameter – Ed Heal Oct 25 '14 at 06:35
  • Your code is wrong because `void func()` means "function taking no arguments", but you provided an argument. Hence the error message "too many arguments to function". – M.M Oct 25 '14 at 06:47
  • @Matt I intentionally caused a compiler error to show the commenter that the type does get "rewritten". – user4179986 Oct 25 '14 at 06:48
  • OK although the standard quote is a better demonstration of that than the compiler error :) – M.M Oct 25 '14 at 06:48
  • Your first paragraph is wrong. Rewriting of types in a parameter is nothing to do with array-pointer decay. The thing where `int []` in a parameter list actually means `int *` is not decay, it's just a dumb syntax thing. – M.M Oct 25 '14 at 06:50
  • If you grep the standard for "function-to-pointer" you will see all of the cases where that conversion is applied – M.M Oct 25 '14 at 06:52
  • Not what you asked, but perhaps interesting nonetheless: the most notable place where a function gets converted to a pointer-to-function in C is every time you call one (function calls are only defined for pointer-to-functions, not for functions), but C++ dropped that and explicitly states that functions can be called and that the conversion to pointer-to-function is suppressed when calling a function. –  Oct 25 '14 at 07:03

4 Answers4

15

There are three conversions that are considered lvalue transformations: lvalue-to-rvalue, array-to-pointer, and function-to-pointer. You can call this "decay" since that's what std::decay will do to these types, but the standard just calls this a function-to-pointer conversion [conv.func]:

An lvalue of function type T can be converted to a prvalue of type “pointer to T.” The result is a pointer to the function.

If you're asking for what the cases are for when a function-to-pointer conversion happens, they are basically the same as when the other two lvalue transformations would happen. If we just go through the standard in order, the following is an exhaustive list of cases where function-to-pointer conversion happens:

Using a function as an operand, [expr]/9:

Whenever a glvalue expression appears as an operand of an operator that expects a prvalue for that operand, the lvalue-to-rvalue (4.1), array-to-pointer (4.2), or function-to-pointer (4.3) standard conversions are applied to convert the expression to a prvalue.

Using a function as an argument to a varargs function, [expr.call]/7:

When there is no parameter for a given argument, the argument is passed in such a way that the receiving function can obtain the value of the argument by invoking va_arg (18.10)... The lvalue-to-rvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) standard conversions are performed on the argument expression.

You can static_cast away this conversion, [expr.static.cast]/7:

The inverse of any standard conversion sequence (Clause 4) not containing an lvalue-to-rvalue (4.1), arrayto- pointer (4.2), function-to-pointer (4.3), null pointer (4.10), null member pointer (4.11), or boolean (4.12) conversion, can be performed explicitly using static_cast.

Though otherwise, the operand you pass in will get converted, [expr.static.cast]/8:

The lvalue-to-rvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) conversions are applied to the operand.

Using reinterpret_cast, [expr.reinterpret.cast]/1:

The result of the expression reinterpret_cast<T>(v) is the result of converting the expression v to type T. If T is an lvalue reference type or an rvalue reference to function type, the result is an lvalue; if T is an rvalue reference to object type, the result is an xvalue; otherwise, the result is a prvalue and the lvalue-torvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) standard conversions are performed on the expression v.

Using const_cast, [expr.const.cast], with basically identical wording to the above. Using the conditional operator, [expr.cond]:

Lvalue-to-rvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) standard conversions are performed on the second and third operands.

Notice in all of the above cases, it's always all of the lvalue transformations.

Function-to-pointer conversions also occur when in templates. Passing a function as a non-type parameter, [temp.arg.nontype]/5.4:

For a non-type template-parameter of type pointer to function, the function-to-pointer conversion (4.3) is applied

Or type deduction, [temp.deduct.call]/2:

If P is not a reference type:

  • — If A is an array type, the pointer type produced by the array-to-pointer standard conversion (4.2) is used in place of A for type deduction; otherwise,
  • — If A is a function type, the pointer type produced by the function-to-pointer standard conversion (4.3) is used in place of A for type deduction; otherwise,

Or conversion function template deduction, with roughly the same wording.

And lastly, of course, std::decay itself, defined in [meta.trans.other], emphasis mine:

Let U be remove_reference_t<T>. If is_array<U>::value is true, the member typedef type shall equal remove_extent_t<U>*. If is_function<U>::value is true, the member typedef type shall equal add_pointer_t<U>. Otherwise the member typedef type equals remove_cv_t<U>. [ Note: This behavior is similar to the lvalue-to-rvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) conversions applied when an lvalue expression is used as an rvalue, but also strips cv-qualifiers from class types in order to more closely model by-value argument passing. —end note ]

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Could you please give some examples for `Using a function as an operand, [expr]/9`? And does the decay in `std::cout << std::hex` belongs to the said kind? – John Apr 15 '22 at 03:07
6

When it's about data types, functions are not first class citizens in C and C++ (the question is about C++ but the behaviour is inherited from C). They are code, not data and they cannot be copied, passed as arguments to functions or returned by functions (but all these can happen to pointers to functions.)

This is why a function name is treated like a pointer to that function and not like the function body itself.

The possibility to use a function (name) instead of a pointer to the function is just a courtesy the language makes to the programmer, and not a "decay".

The same for arrays: they are not copied, not passed as function arguments and not returned by functions. The address of their first element is used instead (copied, passed as function argument or returned by functions). This is why the array name can be used instead of the address of its first element and again, this is just a way to write less (and less obfuscated) code.

For the compiler, a function is a block of memory (that contains code to be executed at some time) that does not move and is identified by its address, i.e. the pointer to the function. An array is also a block of data that does not move and is identified by its address (which is also the address of its first element). Again, this is a pointer.

The concepts of function and array at higher levels (C, C++) are translated by the compiler to primitive values (pointers) that are understood by the lower levels (assembler, machine code).

axiac
  • 68,258
  • 9
  • 99
  • 134
0

Another obvious similarity between arrays and functions would be the following:

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
}
-3

yes they are both pointers, first is a function pointer, second is a pointer to a block of ints. * looks like a point.

Andrew Luo
  • 919
  • 1
  • 5
  • 6