Function-to-function-pointer “decay”

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 ]

Leave a Comment