La variabile statica constexpr all'interno di una funzione ha senso?


193

Se ho una variabile all'interno di una funzione (diciamo, un array di grandi dimensioni), ha senso dichiararla entrambe statice constexpr? constexprgarantisce che l'array sia creato in fase di compilazione, quindi sarebbe staticinutile?

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

Sta staticeffettivamente facendo qualcosa lì in termini di codice generato o semantica?

Risposte:


231

La risposta breve è che non solo è staticutile, ma è sempre abbastanza desiderabile.

Innanzitutto, nota che statice constexprsono completamente indipendenti l'uno dall'altro. staticdefinisce la durata dell'oggetto durante l'esecuzione; constexprspecifica che l'oggetto dovrebbe essere disponibile durante la compilazione. Compilazione ed esecuzione sono disgiunte e non contigue, sia nel tempo che nello spazio. Quindi, una volta compilato il programma, constexprnon è più rilevante.

Ogni variabile dichiarata constexprè implicitamente constma conste staticè quasi ortogonale (ad eccezione dell'interazione con static constnumeri interi).

Il C++modello a oggetti (§1.9) richiede che tutti gli oggetti diversi dai bit-field occupino almeno un byte di memoria e abbiano indirizzi; inoltre tutti questi oggetti osservabili in un programma in un determinato momento devono avere indirizzi distinti (paragrafo 6). Ciò non richiede al compilatore di creare un nuovo array nello stack per ogni invocazione di una funzione con un array const non statico locale, poiché il compilatore potrebbe rifugiarsi nel as-ifprincipio a condizione che possa dimostrare che nessun altro oggetto del genere può essere osservato.

Questo non sarà facile da dimostrare, sfortunatamente, a meno che la funzione non sia banale (ad esempio, non chiama alcuna altra funzione il cui corpo non è visibile all'interno dell'unità di traduzione) perché gli array, più o meno per definizione, sono indirizzi. Quindi nella maggior parte dei casi, l' const(expr)array non statico dovrà essere ricreato nello stack ad ogni invocazione, il che sconfigge il punto di poterlo calcolare al momento della compilazione.

D'altra parte, un static constoggetto locale è condiviso da tutti gli osservatori e inoltre può essere inizializzato anche se la funzione in cui è definito non viene mai chiamata. Quindi nessuno dei precedenti si applica e un compilatore è libero non solo di generare una sola istanza di esso; è libero di generarne una singola istanza nella memoria di sola lettura.

Quindi dovresti assolutamente usare static constexprnel tuo esempio.

Tuttavia, c'è un caso in cui non vorresti usare static constexpr. A meno che un constexproggetto dichiarato non sia utilizzato o dichiarato ODRstatic , il compilatore è libero di non includerlo affatto. È piuttosto utile, perché consente l'uso di constexprarray temporanei in fase di compilazione senza inquinare il programma compilato con byte non necessari. In tal caso, non si vorrebbe chiaramente utilizzarlo static, poiché staticè probabile che costringa l'oggetto a esistere in fase di esecuzione.


2
@AndrewLazarus, non puoi lanciarti constda un constoggetto, solo da un punto const X*che indica un X. Ma non è questo il punto; il punto è che gli oggetti automatici non possono avere indirizzi statici. Come ho detto, constexprcessa di essere significativo una volta terminata la compilazione, quindi non c'è nulla da buttare via (e molto probabilmente nulla, perché l'oggetto non è nemmeno garantito che esista in fase di esecuzione.)
rici

17
Sento che non solo questa risposta è incredibilmente confusa ma anche contraddittoria. Ad esempio, dici che vuoi quasi sempre statice constexprspieghi che sono ortogonali e indipendenti, facendo cose diverse. Quindi si menziona un motivo per NON combinare i due poiché ignorerebbe l'utilizzo ODR (che sembra utile). Oh, e ancora non vedo perché static dovrebbe essere usato con constexpr poiché static è per roba di runtime. Non hai mai spiegato perché statico con constexpr è importante.
void.pointer

2
@ void.pointer: hai ragione sull'ultimo paragrafo. Ho cambiato l'introduzione. Pensavo di aver spiegato l'importanza di static constexpr(evita che l'array costante debba essere ricreato ad ogni chiamata di funzione), ma ho modificato alcune parole che potrebbero renderlo più chiaro. Grazie.
rici,

8
Potrebbe anche essere utile menzionare le costanti di tempo di compilazione e costanti di runtime. In altre parole, se una constexprvariabile costante viene utilizzata solo in contesti di compilazione e non è mai necessaria in fase di runtime, staticnon ha senso, dal momento che si arriva al runtime, il valore è stato effettivamente "incorporato". Tuttavia, se constexprviene utilizzato in contesti di runtime (in altre parole, constexprdovrebbe essere convertito in modo constimplicito e disponibile con un indirizzo fisico per il codice di runtime) vorrà staticgarantire la conformità ODR, ecc. Questa è la mia comprensione, almeno.
void.pointer

3
Un esempio per il mio ultimo commento: static constexpr int foo = 100;. Non c'è motivo per cui il compilatore non possa sostituire fooletteralmente l' uso di ovunque 100, a meno che il codice non stia facendo qualcosa del genere &foo. Quindi, staticsulla foonon ha alcuna utilità in questo caso dal momento che foonon esiste in fase di esecuzione. Ancora una volta tutto fino al compilatore.
void.pointer,

10

Oltre alla risposta data, vale la pena notare che il compilatore non è necessario per inizializzare la constexprvariabile in fase di compilazione, sapendo che la differenza tra constexpred static constexprè che per utilizzarla static constexprè necessario inizializzare la variabile una sola volta.

Il codice seguente mostra come la constexprvariabile viene inizializzata più volte (con lo stesso valore), mentre static constexprsicuramente viene inizializzata una sola volta.

Inoltre, il codice confronta il vantaggio di constexprcontro constin combinazione con 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;
}

Possibile uscita del programma:

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

Come vedi te stesso constexprviene inizializzato più volte (l'indirizzo non è lo stesso) mentre la staticparola chiave assicura che l'inizializzazione venga eseguita una sola volta.


non possiamo usare constexpr const short constexpr_shortper dare un errore se constexpr_short è inizializzato di nuovo
akhileshzmishra

la tua sintassi di constexpr constnon ha senso perché lo constexprè già const, l'aggiunta di constuna o più volte viene ignorata dal compilatore. Stai cercando di rilevare un errore, ma questo non è un errore, è così che funziona la maggior parte dei compilatori.
metablaster
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.