Constexpr-Funktionen werden zur Kompilierzeit nicht aufgerufen, wenn das Ergebnis ignoriert wird

Aug 15 2020

Ich habe einige ziemlich seltsame Codeabdeckungsergebnisse von constexprFunktionen untersucht (Aufrufe zur Kompilierzeit können nicht von dem von mir verwendeten Codeabdeckungstool instrumentiert werden) und bemerkte, dass einige constexprFunktionen als Laufzeitfunktionen aufgerufen wurden , wenn die Ergebnisse der Funktion Anruf wurden nicht gespeichert .

Es scheint, dass für constexprFunktionen oder Methoden, wenn Sie die Ergebnisse des Aufrufs speichern (entweder in einer Laufzeitvariablen [Hervorhebung!!!] oder einer constexprVariablen), 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 constexpres 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 constexprFunktionen als Laufzeit aufgerufen werden? constexprEs 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 constexprMethoden 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

Jarod42 Aug 15 2020 at 14:03

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