F.4 — Funkcje Constexpr (część 4) 4)

Funkcje Constexpr/consteval mogą używać zmiennych lokalnych innych niż stałe

W ramach funkcji constexpr lub constexpr możemy używać zmiennych lokalnych, które nie są constexpr, a wartość tych zmiennych można zmieniać.

Głupie przykład:

#include <iostream>

consteval int doSomething(int x, int y) // function is consteval
{
    x = x + 2;       // we can modify the value of non-const function parameters

    int z { x + y }; // we can instantiate non-const local variables
    if (x > y)
        z = z - 1;   // and then modify their values

    return z;
}

int main()
{
    constexpr int g { doSomething(5, 6) };
    std::cout << g << '\n';

    return 0;
}

Gdy takie funkcje są oceniane w czasie kompilacji, kompilator zasadniczo „wykonuje” tę funkcję i zwraca obliczoną wartość.

Funkcje Constexpr/consteval mogą używać parametrów funkcji i zmiennych lokalnych jako argumentów w wywołaniach funkcji constexpr

Powyżej zauważyliśmy, że „Kiedy funkcja constexpr (lub consteval) jest oceniana w czasie kompilacji, wszystkie inne funkcje, które wywołuje, muszą zostać ocenione w czasie kompilacji.”

Być może zaskakujące, funkcja constexpr lub constexpr może używać swoich parametrów funkcji (które nie są constexpr) lub nawet zmiennych lokalnych (które mogą w ogóle nie być constexpr) jako argumentów w wywołaniu funkcji constexpr. Kiedy funkcja constexpr lub consteval jest oceniana w czasie kompilacji, kompilator musi znać wartość wszystkich parametrów funkcji i zmiennych lokalnych (w przeciwnym razie nie mógłby ich ocenić w czasie kompilacji). Dlatego w tym konkretnym kontekście C++ pozwala na użycie tych wartości jako argumentów w wywołaniu funkcji constexpr, a wywołanie tej funkcji constexpr może być nadal oceniane w czasie kompilacji.

#include <iostream>

constexpr int goo(int c) // goo() is now constexpr
{
    return c;
}

constexpr int foo(int b) // b is not a constant expression within foo()
{
    return goo(b);       // if foo() is resolved at compile-time, then `goo(b)` can also be resolved at compile-time
}

int main()
{
    std::cout << foo(5);
    
    return 0;
}

W powyższym przykładzie foo(5) może zostać ocenione w czasie kompilacji lub nie. Jeśli tak, to kompilator o tym wie b Jest 5. I chociaż b nie jest constexpr, kompilator może potraktować wywołanie goo(b) tak, jakby było goo(5) i ocenić to wywołanie funkcji w czasie kompilacji. Jeśli zamiast tego foo(5) zostanie rozwiązany w czasie wykonywania, wówczas goo(b) zostanie również rozwiązany w czasie wykonywania.

Czy funkcja constexpr może wywołać funkcję inną niż constexpr?

Odpowiedź brzmi tak, ale tylko wtedy, gdy funkcja constexpr jest oceniana w niestałym kontekście. Funkcja inna niż constexpr nie może zostać wywołana, gdy funkcja constexpr wykonuje ocenę w stałym kontekście (ponieważ wtedy funkcja constexpr nie byłaby w stanie wygenerować stałej wartości w czasie kompilacji), co spowoduje błąd kompilacji.

Wywołanie funkcji innej niż constexpr jest dozwolone, aby funkcja constexpr mogła zrobić coś takiego:

#include <type_traits> // for std::is_constant_evaluated

constexpr int someFunction()
{
    if (std::is_constant_evaluated()) // if evaluating in constant context
        return someConstexprFcn();
    else
        return someNonConstexprFcn();
}

Teraz rozważ to wariant:

constexpr int someFunction(bool b)
{
    if (b)
        return someConstexprFcn();
    else
        return someNonConstexprFcn();
}

Jest to dozwolone, o ile someFunction(false) nie jest nigdy wywoływane w wyrażeniu stałym.

Na marginesie…

Przed C++23 standard C++ mówi, że funkcja constexpr musi zwracać wartość constexpr dla co najmniej jednego zestawu argumentów, w przeciwnym razie jest ona źle sformułowana technicznie. Bezwarunkowe wywołanie funkcji innej niż constexpr w funkcji constexpr powoduje, że funkcja constexpr jest źle sformułowana. Jednakże kompilatory nie muszą generować błędów ani ostrzeżeń w takich przypadkach - dlatego kompilator prawdopodobnie nie będzie narzekał, chyba że spróbujesz wywołać taką funkcję constexpr w stałym kontekście. W C++23 ten wymóg został uchylony.

Aby uzyskać najlepsze wyniki, zalecamy, co następuje:

  1. Jeśli to możliwe, unikaj wywoływania funkcji innych niż constexpr z poziomu funkcji constexpr.
  2. Jeśli funkcja constexpr wymaga innego zachowania w kontekstach stałych i niestałych, warunkowaj zachowanie za pomocą if (std::is_constant_evaluated()) (w C++20) lub if consteval (C++ 23 i nowsze).
  3. Zawsze testuj funkcje constexpr w stałym kontekście, ponieważ mogą działać, gdy zostaną wywołane w niestałym kontekście, ale zawiodą w stałym kontekście.

Kiedy powinienem constexpr funkcji?

Ogólną zasadą jest, że jeśli funkcja może być oceniana jako część wymaganego wyrażenia stałego, powinna być made constexpr.

A czysta funkcja to funkcja spełniająca następujące kryteria:

  • Funkcja zwraca zawsze ten sam wynik po podaniu tych samych argumentów
  • Funkcja nie ma skutków ubocznych (np. nie zmienia wartości statycznych zmiennych lokalnych lub globalnych, nie wykonuje operacji wejściowych ani wyjściowych itp.).

Czyste funkcje powinny generalnie mieć postać constexpr.

Na marginesie…

Funkcje Constexpr nie zawsze muszą być czyste. W C++ 23 funkcje constexpr mogą używać i modyfikować statyczne zmienne lokalne. Ponieważ wartość statycznej zmiennej lokalnej jest zachowywana we wszystkich wywołaniach funkcji, modyfikowanie statycznej zmiennej lokalnej jest uważane za efekt uboczny.

To powiedziawszy, jeśli twój program jest trywialny lub do wyrzucenia i nie skonteksturujesz funkcji, świat się nie skończy. Miejmy nadzieję.

Najlepsza praktyka

Jeśli nie masz konkretnego powodu, aby tego nie robić, należy utworzyć funkcję, która może być oceniana jako część wyrażenia stałego constexpr (nawet jeśli nie jest obecnie używana w ten sposób).

Funkcja, która nie może być oceniana jako część wymaganego wyrażenia stałego, nie powinna być oznaczona jako constexpr.

Dlaczego nie constexpr każdej funkcji?

Jest kilka powodów, dla których możesz nie chcieć do constexpr funkcji:

  1. constexpr sygnalizuje, że funkcja może zostać użyta w wyrażeniu stałym. Jeśli Twojej funkcji nie można ocenić jako części wyrażenia stałego, nie należy jej oznaczać jako constexpr.
  2. constexpr będącą częścią interfejsu funkcji. Gdy funkcja zostanie utworzona jako constexpr, może być wywoływana przez inne funkcje constexpr lub używana w kontekstach wymagających wyrażeń stałych. Usunięcie constexpr później spowoduje uszkodzenie takiego kodu.
  3. constexpr Funkcje mogą być trudniejsze do debugowania, ponieważ nie można przerwać ich ani przejść przez nie w debugerze.

Po co constexpr funkcję, skoro nie jest ona faktycznie oceniana w czasie kompilacji?

Nowi programiści czasami pytają: „dlaczego powinienem konteksturować funkcję, jeśli jest ona oceniana tylko w czasie wykonywania w moim programie (np. ponieważ argumenty w wywołaniu funkcji nie są stałe)”?

Jest kilka powodów:

  1. Używanie constexpr ma niewielką wadę i może pomóc kompilatorowi zoptymalizować program tak, aby był mniejszy i szybszy.
  2. To, że nie wywołujesz funkcji w kontekście możliwym do oceny w czasie kompilacji, nie oznacza, że nie wywołasz jej w takim kontekście podczas modyfikowania lub rozszerzania programu. A jeśli jeszcze nie skonstruowałeś tej funkcji, możesz nie pomyśleć o tym, kiedy zaczniesz ją wywoływać w takim kontekście, a wtedy stracisz korzyści związane z wydajnością. Możesz też zostać zmuszony do jego późniejszej interpretacji, gdy będziesz musiał użyć zwracanej wartości w kontekście wymagającym gdzieś stałego wyrażenia.
  3. Powtarzanie pomaga utrwalić najlepsze praktyki.

W przypadku nietrywialnego projektu dobrym pomysłem jest wdrożenie funkcji z myślą, że mogą zostać ponownie wykorzystane (lub rozszerzone) w przyszłości. Za każdym razem, gdy modyfikujesz istniejącą funkcję, ryzykujesz jej uszkodzeniem, a to oznacza, że ​​należy ją ponownie przetestować, co wymaga czasu i energii. Często warto poświęcić dodatkową minutę lub dwie na „zrobienie tego dobrze za pierwszym razem”, aby nie trzeba było powtarzać tego (i ponownie testować) później.

guest
Twój adres e-mail nie zostanie wyświetlony
Znalazłeś błąd? Zostaw komentarz powyżej!
Komentarze związane z poprawkami zostaną usunięte po przetworzeniu, aby pomóc zmniejszyć bałagan. Dziękujemy za pomoc w ulepszaniu witryny dla wszystkich!
Awatary z https://gravatar.com/ są połączone z podanym adresem e-mail.
Powiadamiaj mnie o odpowiedziach:  
13 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze