Czy zmienna static constexpr w funkcji ma sens?

193

Jeśli mam zmienną wewnątrz funkcji (powiedzmy, dużą tablicę), czy sens ma deklarowanie jej jednocześnie statici constexpr? constexprgwarantuje, że tablica jest tworzona w czasie kompilacji, więc czy staticbyłaby bezużyteczna?

void f() {
    static constexpr int x [] = {
        // a few thousand elements
    };
    // do something with the array
}

Czy staticfaktycznie coś tam robi, jeśli chodzi o generowany kod lub semantykę?

David Stone
źródło

Odpowiedzi:

230

Krótka odpowiedź jest taka, że ​​nie tylko jest staticprzydatna, ale też całkiem dobrze będzie zawsze pożądana.

Po pierwsze, zauważ to statici constexprsą całkowicie od siebie niezależne. staticokreśla czas życia obiektu podczas wykonywania; constexprokreśla, że ​​obiekt powinien być dostępny podczas kompilacji. Kompilacja i wykonanie są rozłączne i niejednoznaczne, zarówno w czasie, jak i przestrzeni. Po skompilowaniu program constexprprzestaje być istotny.

Każda zadeklarowana zmienna constexprjest domyślnie, constale consti staticprawie ortogonalna (z wyjątkiem interakcji z static constliczbami całkowitymi).

Model C++obiektowy (§ 1.9) wymaga, aby wszystkie obiekty inne niż pola bitowe zajmowały co najmniej jeden bajt pamięci i miały adresy; ponadto wszystkie takie obiekty, które w danym momencie można zaobserwować w programie, muszą mieć odrębne adresy (pkt 6). Nie wymaga to od kompilatora utworzenia nowej tablicy na stosie dla każdego wywołania funkcji z lokalną niestatyczną stałą tablicą const, ponieważ kompilator może schronić się w as-ifzasadzie, pod warunkiem, że może udowodnić, że żaden inny taki obiekt nie może być zauważony.

Niestety nie będzie to łatwe do udowodnienia, chyba że funkcja jest trywialna (na przykład nie wywołuje żadnej innej funkcji, której treść nie jest widoczna w jednostce tłumaczenia), ponieważ tablice, mniej więcej z definicji, są adresami. Dlatego w większości przypadków const(expr)tablica niestatyczna będzie musiała zostać odtworzona na stosie przy każdym wywołaniu, co nie pozwala na obliczenie jej w czasie kompilacji.

Z drugiej strony, static constobiekt lokalny jest wspólny dla wszystkich obserwatorów, a ponadto może zostać zainicjowany, nawet jeśli funkcja, w której jest zdefiniowany, nigdy nie zostanie wywołana. Żadne z powyższych postanowień nie ma zastosowania, a kompilator może nie tylko wygenerować tylko jedną jego instancję; wygenerowanie pojedynczego wystąpienia w pamięci tylko do odczytu jest bezpłatne.

Więc zdecydowanie powinieneś użyć static constexprw swoim przykładzie.

Jest jednak jeden przypadek, w którym nie chcesz używać static constexpr. O ile constexprzadeklarowany obiekt nie jest używany ani deklarowany ODRstatic , kompilator może go w ogóle nie uwzględniać. Jest to bardzo przydatne, ponieważ pozwala na użycie tymczasowych constexprtablic w czasie kompilacji bez zanieczyszczania skompilowanego programu niepotrzebnymi bajtami. W takim przypadku najwyraźniej nie chcesz używać static, ponieważ staticprawdopodobnie zmusi obiekt do istnienia w czasie wykonywania.

rici
źródło
2
@AndrewLazarus, nie można wyrzucać constz constobiektu, jedynie z const X*którego punkty do X. Ale nie o to chodzi; chodzi o to, że obiekty automatyczne nie mogą mieć adresów statycznych. Jak powiedziałem, constexprprzestaje mieć znaczenie po zakończeniu kompilacji, więc nie ma nic do odrzucenia (i całkiem możliwe, że w ogóle, ponieważ nie można zagwarantować, że obiekt istnieje w środowisku wykonawczym.)
rici
17
Mam wrażenie, że nie tylko ta odpowiedź jest niewiarygodnie myląca, ale także wewnętrznie sprzeczna. Na przykład można powiedzieć, że prawie zawsze chce statica constexprjednak wyjaśnić, że są prostopadłe i niezależna, robi różne rzeczy. Następnie podajesz powód, aby NIE łączyć tych dwóch, ponieważ zignorowałoby to użycie ODR (co wydaje się przydatne). Aha i wciąż nie rozumiem, dlaczego statyczny powinien być używany z constexpr, ponieważ statyczny jest przeznaczony do środowiska wykonawczego. Nigdy nie wyjaśniłeś, dlaczego statyczny z constexpr jest ważny.
void.pointer
2
@ void.pointer: Masz rację co do ostatniego akapitu. Zmieniłem wprowadzenie. Myślałem, że wyjaśniłem znaczenie static constexpr(nie pozwala na odtworzenie stałej tablicy przy każdym wywołaniu funkcji), ale poprawiłem kilka słów, które mogłyby to uczynić bardziej zrozumiałym. Dzięki.
rici
8
Przydatne może być również wspomnienie o kompilacji stałych czasowych vs stałych wykonawczych. Innymi słowy, jeśli constexprstała zmienna jest używana tylko w kontekstach czasu kompilacji i nigdy nie jest potrzebna w środowisku wykonawczym, to staticnie ma sensu, ponieważ do momentu przejścia do środowiska wykonawczego wartość została skutecznie „wstawiona”. Jeśli jednak constexprzostanie użyty w kontekście środowiska wykonawczego (innymi słowy, constexprmusiałby zostać przekonwertowany na constniejawnie i dostępny z fizycznym adresem dla kodu środowiska wykonawczego), będzie chciał staticzapewnić zgodność z ODR itp. Tak przynajmniej rozumiem.
void.pointer
3
Przykładem dla mojego ostatniego komentarza: static constexpr int foo = 100;. Nie ma powodu, dla którego kompilator nie mógłby zastąpić foodosłownego użycia wszędzie 100, chyba że kod działałby podobnie &foo. Więc staticon fooma przydatność w tym przypadku, ponieważ foonie istnieje w czasie wykonywania. Znowu wszystko do kompilatora.
void.pointer
10

Oprócz podanej odpowiedzi warto zauważyć, że kompilator nie jest wymagany do inicjalizacji constexprzmiennej w czasie kompilacji, wiedząc, że różnica między constexpri static constexprpolega na tym, że użycie static constexprzapewnia, że ​​zmienna jest inicjowana tylko raz.

Poniższy kod pokazuje, w jaki sposób constexprzmienna jest inicjowana wiele razy (z tą samą wartością), a na static constexprpewno jest inicjowana tylko raz.

Ponadto kod porównuje przewagę opcji constexprprzeciw constw połączeniu z static.

#include <iostream>
#include <string>
#include <cassert>
#include <sstream>

const short const_short = 0;
constexpr short constexpr_short = 0;

// print only last 3 address value numbers
const short addr_offset = 3;

// This function will print name, value and address for given parameter
void print_properties(std::string ref_name, const short* param, short offset)
{
    // determine initial size of strings
    std::string title = "value \\ address of ";
    const size_t ref_size = ref_name.size();
    const size_t title_size = title.size();
    assert(title_size > ref_size);

    // create title (resize)
    title.append(ref_name);
    title.append(" is ");
    title.append(title_size - ref_size, ' ');

    // extract last 'offset' values from address
    std::stringstream addr;
    addr << param;
    const std::string addr_str = addr.str();
    const size_t addr_size = addr_str.size();
    assert(addr_size - offset > 0);

    // print title / ref value / address at offset
    std::cout << title << *param << " " << addr_str.substr(addr_size - offset) << std::endl;
}

// here we test initialization of const variable (runtime)
void const_value(const short counter)
{
    static short temp = const_short;
    const short const_var = ++temp;
    print_properties("const", &const_var, addr_offset);

    if (counter)
        const_value(counter - 1);
}

// here we test initialization of static variable (runtime)
void static_value(const short counter)
{
    static short temp = const_short;
    static short static_var = ++temp;
    print_properties("static", &static_var, addr_offset);

    if (counter)
        static_value(counter - 1);
}

// here we test initialization of static const variable (runtime)
void static_const_value(const short counter)
{
    static short temp = const_short;
    static const short static_var = ++temp;
    print_properties("static const", &static_var, addr_offset);

    if (counter)
        static_const_value(counter - 1);
}

// here we test initialization of constexpr variable (compile time)
void constexpr_value(const short counter)
{
    constexpr short constexpr_var = constexpr_short;
    print_properties("constexpr", &constexpr_var, addr_offset);

    if (counter)
        constexpr_value(counter - 1);
}

// here we test initialization of static constexpr variable (compile time)
void static_constexpr_value(const short counter)
{
    static constexpr short static_constexpr_var = constexpr_short;
    print_properties("static constexpr", &static_constexpr_var, addr_offset);

    if (counter)
        static_constexpr_value(counter - 1);
}

// final test call this method from main()
void test_static_const()
{
    constexpr short counter = 2;

    const_value(counter);
    std::cout << std::endl;

    static_value(counter);
    std::cout << std::endl;

    static_const_value(counter);
    std::cout << std::endl;

    constexpr_value(counter);
    std::cout << std::endl;

    static_constexpr_value(counter);
    std::cout << std::endl;
}

Możliwe wyjście programu:

value \ address of const is               1 564
value \ address of const is               2 3D4
value \ address of const is               3 244

value \ address of static is              1 C58
value \ address of static is              1 C58
value \ address of static is              1 C58

value \ address of static const is        1 C64
value \ address of static const is        1 C64
value \ address of static const is        1 C64

value \ address of constexpr is           0 564
value \ address of constexpr is           0 3D4
value \ address of constexpr is           0 244

value \ address of static constexpr is    0 EA0
value \ address of static constexpr is    0 EA0
value \ address of static constexpr is    0 EA0

Jak widać, constexprinicjowany jest wiele razy (adres nie jest taki sam), a staticsłowo kluczowe zapewnia, że ​​inicjalizacja jest wykonywana tylko raz.

metablaster
źródło
czy nie możemy użyć constexpr const short constexpr_shortdo podania błędu, jeśli constexpr_short zostanie ponownie zainicjowany
akhileshzmishra
twoja składnia constexpr constnie ma sensu, ponieważ constexprjuż jest const, dodawanie constraz lub wiele razy jest ignorowane przez kompilator. Próbujesz wychwycić błąd, ale to nie jest błąd, tak działa większość kompilatorów.
metablaster