Funzioni Constexpr non chiamate in fase di compilazione se il risultato viene ignorato

Aug 15 2020

Stavo indagando su alcuni risultati di constexprfunzioni di copertura del codice piuttosto strani (le chiamate in fase di compilazione non possono essere strumentate dallo strumento di copertura del codice che utilizzo) e ho notato che alcune constexprfunzioni venivano chiamate come funzioni di runtime , se i risultati della funzione la chiamata non è stata memorizzata .

Sembra che, per constexprfunzioni o metodi, se memorizzi i risultati della chiamata (o in una variabile di runtime [enfasi!!!] o in una constexprvariabile), la chiamata è una chiamata in fase di compilazione (purché i parametri siano compilati -volta). Se ignori i risultati, la chiamata è una chiamata di runtime. Questo non ha nulla a che fare con il mio strumento di copertura del codice; il comportamento sembra essere ripetibile nel semplice esempio riportato di seguito.

Si potrebbe sostenere che poiché constexprle funzioni non possono avere effetti collaterali, non importa cosa fa il compilatore se non si restituisce/si utilizza un risultato. Penserei che per efficienza, il compilatore farebbe comunque tutto ciò che può essere constexpr, ma non è né qui né là ... Quello che mi chiedo è se questo sia anche un comportamento definito.

È un modo portatile per garantire che le tue constexprfunzioni vengano richiamate come runtime ??? Non ci sono un sacco di usi per questo, ma un uso è: se vuoi "prenderti il ​​merito per i test che sono stati chiamati sulle constexprfunzioni" nella copertura del codice, semplicemente chiamando la stessa funzione alla fine del tuo unit test, e ignorando il risultato, in modo che vengano strumentati.

Esistono altri modi per forzare una funzione a essere chiamata come runtime, lo so, sono soprattutto curioso di sapere cosa sta succedendo qui. È stato molto inaspettato quando l'ho visto per la prima volta. A meno che questo non sia portatile, probabilmente chiamerò i miei constexprmetodi semplicemente tramite un oggetto di runtime (che sembra fare il trucco anche per i metodi statici) o tramite un puntatore di funzione di runtime.

Vedere l'esempio di seguito. Dimostrazione dal 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!
}

Risposte

Jarod42 Aug 15 2020 at 14:03

Potrebbe essere fonte di confusione, ma le funzioni constexpr dovrebbero essere chiamate in fase di compilazione solo in contesti constexpr (assegnazione alla variabile constexpr, utilizzo per la dimensione dell'array o il parametro del modello, ...).

In un contesto regolare, le funzioni vengono chiamate in fase di esecuzione. L'ottimizzatore potrebbe risolvere quella funzione in fase di compilazione (come per qualsiasi altra funzione che segue la regola as-if). constexprè davvero un buon suggerimento per l'ottimizzazione.

Si potrebbe sostenere che poiché le funzioni constexpr non possono avere effetti collaterali

Possono avere effetti collaterali, vedere il seguente esempio valido:

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
}

Demo