¿Puedo tomar la dirección de una función definida en la biblioteca estándar?
Considere el siguiente código:
#include <cctype>
#include <functional>
#include <iostream>
int main()
{
std::invoke(std::boolalpha, std::cout); // #1
using ctype_func = int(*)(int);
char c = std::invoke(static_cast<ctype_func>(std::tolower), 'A'); // #2
std::cout << c << "\n";
}
Aquí, las dos llamadas a std::invoke
están etiquetadas para referencia futura. El resultado esperado es:
a
¿Está garantizado el resultado esperado en C++20?
(Nota: hay dos funciones llamadas tolower
: una in <cctype>
y la otra in <locale>
. Se introduce la conversión explícita para seleccionar la sobrecarga deseada).
Respuesta corta
No.
Explicación
[espacio de nombres.std] dice:
Denotemos
F
una función de biblioteca estándar ( [global.functions] ), una función miembro estática de biblioteca estándar o una instancia de una plantilla de función de biblioteca estándar. A menos queF
se designe una función direccionable , el comportamiento de un programa C++ no está especificado (posiblemente mal formado) si explícita o implícitamente intenta formar un puntero aF
. [ Nota: Los posibles medios para formar dichos punteros incluyen la aplicación del&
operador unario ( [expr.unary.op] ),addressof
( [specialized.addressof] ) o una conversión estándar de función a puntero ( [conv.func] ). - nota final ] Además, el comportamiento de un programa C++ no está especificado (posiblemente mal formado) si intenta formar una referenciaF
o si intenta formar un puntero a miembro que designe una función miembro no estática de la biblioteca estándar. ( [member.functions] ) o una creación de instancias de una plantilla de función miembro de biblioteca estándar.
Con esto en mente, revisemos las dos llamadas a std::invoke
.
la primera llamada
std::invoke(std::boolalpha, std::cout);
Aquí, estamos intentando formar un puntero a std::boolalpha
. Afortunadamente, [fmtflags.manip] salva el día:
Cada función especificada en esta subcláusula es una función direccionable designada ( [namespace.std] ).
Y boolalpha
es una función especificada en esta subcláusula. Por tanto, esta línea está bien formada y equivale a:
std::cout.setf(std::ios_base::boolalpha);
¿Pero por qué es eso? Bueno, es necesario el siguiente código:
std::cout << std::boolalpha;
la segunda llamada
std::cout << std::invoke(static_cast<ctype_func>(std::tolower), 'A') << "\n";
Desafortunadamente, [cctype.syn] dice:
El contenido y el significado del encabezado
<cctype>
son los mismos que los del encabezado de la biblioteca estándar C.<ctype.h>
En ninguna parte se tolower
designa explícitamente una función direccionable.
Por lo tanto, el comportamiento de este programa C++ no está especificado (posiblemente mal formado), porque intenta formar un puntero a tolower
, que no está designado como función direccionable.
Conclusión
El resultado esperado no está garantizado. De hecho, ni siquiera se garantiza que el código se compile.
Esto también se aplica a las funciones miembro. [namespace.std] no menciona esto explícitamente, pero se puede ver en [member.functions] que el comportamiento de un programa C++ no está especificado (posiblemente mal formado) si intenta tomar la dirección de una función miembro declarada. en la biblioteca estándar de C++. Por [miembro.funciones]/2 :
Para una función miembro no virtual descrita en la biblioteca estándar de C++, una implementación puede declarar un conjunto diferente de firmas de funciones miembro, siempre que cualquier llamada a la función miembro que seleccione una sobrecarga del conjunto de declaraciones descritas en este documento se comporte como si se seleccionara esa sobrecarga. [ Nota: Por ejemplo, una implementación puede agregar parámetros con valores predeterminados, o reemplazar una función miembro con argumentos predeterminados con dos o más funciones miembro con comportamiento equivalente, o agregar firmas adicionales para el nombre de una función miembro. - nota final ]
Y [expr.unary.op]/6 :
La dirección de una función sobrecargada solo se puede tomar en un contexto que determina de forma única a qué versión de la función sobrecargada se hace referencia (ver [over.over]). [ Nota: dado que el contexto puede determinar si el operando es una función miembro estática o no estática, el contexto también puede afectar si la expresión tiene el tipo "puntero a función" o "puntero a función miembro". - nota final ]
Por lo tanto, el comportamiento de un programa no está especificado (posiblemente mal formado) si intenta explícita o implícitamente formar un puntero a una función miembro en la biblioteca de C++.
(¡Gracias por el comentario por señalar esto!)