Constexpr-Funktionen werden zur Kompilierzeit nicht aufgerufen, wenn das Ergebnis ignoriert wird
Ich habe einige ziemlich seltsame Codeabdeckungsergebnisse von constexpr
Funktionen untersucht (Aufrufe zur Kompilierzeit können nicht von dem von mir verwendeten Codeabdeckungstool instrumentiert werden) und bemerkte, dass einige constexpr
Funktionen als Laufzeitfunktionen aufgerufen wurden , wenn die Ergebnisse der Funktion Anruf wurden nicht gespeichert .
Es scheint, dass für constexpr
Funktionen oder Methoden, wenn Sie die Ergebnisse des Aufrufs speichern (entweder in einer Laufzeitvariablen [Hervorhebung!!!] oder einer constexpr
Variablen), der Aufruf ein Aufruf zur Kompilierzeit ist (solange die Parameter kompiliert werden -Zeit). Wenn Sie die Ergebnisse ignorieren, ist der Aufruf ein Laufzeitaufruf. Das hat nichts mit meinem Code-Coverage-Tool zu tun; das Verhalten scheint in dem einfachen Beispiel unten wiederholbar zu sein.
Sie könnten argumentieren, dass constexpr
es keine Rolle spielt, was der Compiler tut, wenn Sie kein Ergebnis zurückgeben / verwenden, da Funktionen keine Nebenwirkungen haben können. Ich würde denken, dass der Compiler aus Effizienzgründen immer noch alles machen würde, was es sein kann constexpr
, aber das ist weder hier noch dort ... Was ich mich frage, ist, ob dies überhaupt definiertes Verhalten ist.
Ist dies eine portable Methode, um sicherzustellen, dass Ihre constexpr
Funktionen als Laufzeit aufgerufen werden? constexpr
Es gibt nicht viele Verwendungsmöglichkeiten dafür, aber eine Verwendung ist: Wenn Sie in der Codeabdeckung " Tests anerkennen möchten, die für Funktionen aufgerufen wurden ", indem Sie einfach dieselbe Funktion am Ende Ihres Komponententests aufrufen, und das Ergebnis ignorieren, damit sie instrumentiert werden.
Es gibt andere Möglichkeiten, eine Funktion dazu zu zwingen, als Laufzeit aufgerufen zu werden, ich weiß, ich bin hauptsächlich neugierig, was hier vor sich geht. Es war sehr unerwartet, als ich es zum ersten Mal sah. Sofern dies nicht portabel ist, werde ich meine constexpr
Methoden wahrscheinlich nur über ein Laufzeitobjekt aufrufen (was anscheinend sogar für statische Methoden funktioniert) oder über einen Laufzeitfunktionszeiger.
Siehe Beispiel unten. Live-Demo: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!
}
Antworten
Es mag verwirrend sein, aber constexpr-Funktionen sollten zur Kompilierzeit nur in constexpr-Kontexten aufgerufen werden (Zuweisung an constexpr-Variable, Verwendung für Array-Größe oder Vorlagenparameter, ...).
Im regulären Kontext werden Funktionen zur Laufzeit aufgerufen. Der Optimierer löst diese Funktion möglicherweise zur Kompilierzeit auf (wie bei allen anderen Funktionen, die der Als-ob-Regel folgen). constexpr
ist in der Tat ein guter Hinweis für eine Optimierung.
Sie könnten argumentieren, dass constexpr-Funktionen keine Nebenwirkungen haben können
Sie können Nebenwirkungen haben, siehe folgendes valides Beispiel:
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