Funzioni Constexpr non chiamate in fase di compilazione se il risultato viene ignorato
Stavo indagando su alcuni risultati di constexpr
funzioni 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 constexpr
funzioni venivano chiamate come funzioni di runtime , se i risultati della funzione la chiamata non è stata memorizzata .
Sembra che, per constexpr
funzioni o metodi, se memorizzi i risultati della chiamata (o in una variabile di runtime [enfasi!!!] o in una constexpr
variabile), 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é constexpr
le 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 constexpr
funzioni 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 constexpr
funzioni" 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 constexpr
metodi 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
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