¿Puedo tomar la dirección de una función definida en la biblioteca estándar?

Resuelto L. F. asked hace 5 años • 1 respuestas

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::invokeestá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).

L. F. avatar Apr 15 '19 17:04 L. F.
Aceptado

Respuesta corta

No.

Explicación

[espacio de nombres.std] dice:

Denotemos Funa 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 que Fse 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 a F. [ 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 referencia Fo 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 boolalphaes 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 tolowerdesigna 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!)

L. F. avatar Apr 15 '2019 10:04 L. F.