Las funciones de Constexpr no se llaman en tiempo de compilación si se ignora el resultado

Aug 15 2020

Estaba investigando algunos resultados de constexprfunciones 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 constexprfunciones se llamaban como funciones de tiempo de ejecución , si los resultados de la función la llamada no fue almacenada .

Parece que, para constexprfunciones o métodos, si almacena los resultados de la llamada (ya sea en una variable de tiempo de ejecución [¡¡énfasis!!!] o en una constexprvariable), 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 constexprlas 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 constexprfunciones 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 constexprfunciones" 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 constexprmé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

Jarod42 Aug 15 2020 at 14:03

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). constexpres 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