Las funciones de Constexpr no se llaman en tiempo de compilación si se ignora el resultado
Estaba investigando algunos resultados de constexpr
funciones de cobertura de código bastante extraños (las llamadas en tiempo de compilación no pueden ser instrumentadas por la herramienta de cobertura de código que uso) y noté que algunas constexpr
funciones se llamaban como funciones de tiempo de ejecución , si los resultados de la función la llamada no fue almacenada .
Parece que, para constexpr
funciones o métodos, si almacena los resultados de la llamada (ya sea en una variable de tiempo de ejecución [¡¡énfasis!!!] o en una constexpr
variable), la llamada es una llamada en tiempo de compilación (siempre y cuando los parámetros estén compilados -tiempo). Si ignora los resultados, la llamada es una llamada en tiempo de ejecución. Esto no tiene nada que ver con mi herramienta de cobertura de código; el comportamiento parece ser repetible en el ejemplo simple a continuación.
Podría argumentar que, dado que constexpr
las funciones no pueden tener efectos secundarios, no importa lo que haga el compilador si no devuelve / usa un resultado. Pensaría que, por eficiencia, el compilador seguiría haciendo lo que sea constexpr
, pero eso no es ni aquí ni allá... Lo que me pregunto es si este es un comportamiento definido.
¿Es esta una forma portátil de garantizar que sus constexpr
funciones se invocarán como tiempo de ejecución? No hay muchos usos para eso, pero un uso es: si desea "atribuirse el mérito de las pruebas que se invocaron en constexpr
funciones" en la cobertura de código, simplemente llamando a la misma función al final de su prueba unitaria, y ignorando el resultado, para que se instrumenten.
Hay otras formas de forzar que una función se llame como tiempo de ejecución, lo sé, tengo curiosidad sobre lo que está pasando aquí. Fue muy inesperado cuando lo vi por primera vez. A menos que esto sea portátil, probablemente llamaré a mis constexpr
métodos a través de un objeto de tiempo de ejecución (que parece funcionar incluso para métodos estáticos), o a través de un puntero de función de tiempo de ejecución.
Vea el ejemplo a continuación. Demo en vivo:https://onlinegdb.com/rJao0RNGP
// Modified from https://stackoverflow.com/a/40410624/12854372
extern bool no_symbol;
struct ContextIsConstexpr {
size_t s;
constexpr ContextIsConstexpr() : s(1) {}
constexpr void operator()() {
auto ignore = s ? 1 : no_symbol;
}
};
constexpr bool tryIgnoringResult()
{
ContextIsConstexpr()();
return true;
}
constexpr void thereIsNoResult() {
ContextIsConstexpr()();
}
int main()
{
constexpr auto result1 = tryIgnoringResult(); // OK, compile-time
auto result2 = tryIgnoringResult(); // OK, compile-time
// tryIgnoringResult(); // Unexpected, runtime!
// thereIsNoResult(); // Unexpected, runtime!
}
Respuestas
Puede ser confuso, pero las funciones constexpr deben llamarse en tiempo de compilación solo en contextos constexpr (asignación a la variable constexpr, uso para tamaño de matriz o parámetro de plantilla, ...).
En contexto regular, las funciones se llaman en tiempo de ejecución. Optimizer podría resolver esa función en el momento de la compilación (al igual que para cualquier otra función que siga la regla como si). constexpr
es de hecho una buena pista para que ocurra la optimización.
Podría argumentar que dado que las funciones constexpr no pueden tener efectos secundarios
Pueden tener efectos secundarios, consulte el siguiente ejemplo válido:
constexpr int f(int i)
{
if (i == 0) return 0;
std::cout << i << std::endl;
return i;
}
int main()
{
[[maybe_unused]] constexpr int zero = f(0); // Compile time
f(42); // runtime
}
Manifestación