Kiedy constexpr jest oceniany w czasie kompilacji?

Kiedy constexpr jest oceniany w czasie kompilacji?

Gdy constexpr wywoływana jest funkcja, a wyjście jest przypisane do constexpr zmienna, będzie zawsze uruchamiana w czasie kompilacji.

Oto minimalny przykład:

// Compile with -std=c++14 or later
constexpr int fib(int n) {
    int f0 = 0;
    int f1 = 1;
    for(int i = 0; i < n; i++) {
        int hold = f0 + f1;
        f0 = f1;
        f1 = hold;
    }
    return f0; 
}

int main() {
    constexpr int blarg = fib(10);
    return blarg;
}

Po skompilowaniu w -O0 , gcc wypisuje następujący zestaw dla main :

main:
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], 55
        mov     eax, 55
        pop     rbp
        ret

Pomimo wyłączenia całej optymalizacji, nigdy nie ma żadnego wywołania fib w main sama funkcja.

Dotyczy to powrotu do C++11 , jednak w C++11 fib funkcja musiałaby zostać napisana ponownie, aby użyć konwersji, aby uniknąć użycia zmiennych zmiennych.

Dlaczego kompilator zawiera zestaw dla fib? czasami w pliku wykonywalnym? constexpr funkcja może być używane w czasie wykonywania, a wywołane w czasie wykonywania będzie zachowywać się jak zwykła funkcja.

Używane prawidłowo, constexpr może zapewnić pewne korzyści w zakresie wydajności w określonych przypadkach, ale nacisk na zrobienie wszystkiego constexpr jest więcej o pisaniu kodu, który kompilator może sprawdzić pod kątem niezdefiniowanego zachowania.

Jaki jest przykład constexpr zapewnianie korzyści związanych z wydajnością? Implementując funkcję taką jak std::visit , musisz utworzyć tabelę przeglądową wskaźników do funkcji. Tworzenie tabeli przeglądowej za każdym razem std::visit wywoływana byłaby kosztowna, a przypisanie tabeli wyszukiwania do static zmienna lokalna nadal powodowałaby mierzalne obciążenie, ponieważ program musi sprawdzać, czy ta zmienna została zainicjowana za każdym razem, gdy funkcja jest uruchamiana.

Na szczęście możesz utworzyć tabelę przeglądową constexpr , a kompilator faktycznie wstawi tabelę przeglądową do kodu asemblera funkcji dzięki czemu zawartość tabeli przeglądowej jest znacznie bardziej prawdopodobna w pamięci podręcznej instrukcji, gdy std::visit prowadzony jest.

Czy C++20 zapewnia jakieś mechanizmy gwarantujące, że coś będzie działać w czasie kompilacji?

Jeśli funkcja to consteval , standard określa, że ​​każde wywołanie funkcji musi generować stałą czasu kompilacji.

Może to być trywialnie użyte do wymuszenia oceny w czasie kompilacji dowolnej funkcji constexpr:

template<class T>
consteval T run_at_compiletime(T value) {
    return value;
}

Wszystko podane jako parametr do run_at_compiletime muszą być ocenione w czasie kompilacji:

constexpr int fib(int n) {
    int f0 = 0;
    int f1 = 1;
    for(int i = 0; i < n; i++) {
        int hold = f0 + f1;
        f0 = f1;
        f1 = hold;
    }
    return f0; 
}

int main() {
    // fib(10) will definitely run at compile time
    return run_at_compiletime(fib(10)); 
}

Nigdy; standard C++ pozwala, aby prawie cała kompilacja odbywała się w "runtime". Pewna diagnostyka musi być wykonana w czasie kompilacji, ale nic nie zapobiega szaleństwu kompilatora.

Twój plik binarny może być kopią kompilatora z dołączonym kodem źródłowym, a C++ nie powiedziałby, że kompilator zrobił coś złego.

To, na co patrzysz, to QoI - Jakość implementacji - problem.

W praktyce constexpr zmienne mają tendencję do obliczania czasu kompilacji, a parametry szablonu są zawsze obliczane w czasie kompilacji.

consteval może być również używany do oznaczania funkcji.