Apakah variabel constexpr statis di dalam suatu fungsi masuk akal?

193

Jika saya memiliki variabel di dalam suatu fungsi (katakanlah, array besar), apakah masuk akal untuk menyatakan keduanya staticdan constexpr? constexprmenjamin bahwa array dibuat pada waktu kompilasi, jadi apakah statictidak akan berguna?

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

Apakah staticbenar - benar melakukan sesuatu di sana dalam hal kode atau semantik yang dihasilkan?

David Stone
sumber

Jawaban:

231

Jawaban singkatnya adalah bahwa tidak hanya staticberguna, itu cukup baik selalu akan diinginkan.

Pertama, perhatikan itu staticdan constexprsepenuhnya independen satu sama lain. staticmendefinisikan umur objek selama eksekusi; constexprmenentukan bahwa objek harus tersedia selama kompilasi. Kompilasi dan eksekusi saling terpisah dan terpisah, baik dalam waktu maupun ruang. Jadi begitu program dikompilasi, constexprtidak lagi relevan.

Setiap variabel yang dideklarasikan constexprsecara implisit consttetapi constdan statichampir ortogonal (kecuali untuk interaksi dengan static constbilangan bulat).

Model C++objek (§1.9) mensyaratkan bahwa semua objek selain dari bit-field menempati setidaknya satu byte memori dan memiliki alamat; selanjutnya semua objek yang dapat diamati dalam program pada saat tertentu harus memiliki alamat yang berbeda (paragraf 6). Ini tidak cukup membutuhkan kompilator untuk membuat array baru di stack untuk setiap pemanggilan fungsi dengan array const non-statis lokal, karena kompiler dapat berlindung pada as-ifprinsip asalkan dapat membuktikan bahwa tidak ada objek lain yang dapat dibuat. diamati.

Sayangnya, itu tidak mudah dibuktikan, kecuali fungsinya sepele (misalnya, tidak memanggil fungsi lain yang badannya tidak terlihat di dalam unit terjemahan) karena array, kurang lebih menurut definisi, adalah alamat. Jadi dalam kebanyakan kasus, const(expr)array non-statis harus dibuat ulang pada stack di setiap doa, yang mengalahkan titik untuk dapat menghitungnya pada waktu kompilasi.

Di sisi lain, static constobjek lokal dibagi oleh semua pengamat, dan selanjutnya dapat diinisialisasi bahkan jika fungsi yang didefinisikan tidak pernah disebut. Jadi tidak ada satu pun di atas yang berlaku, dan kompiler bebas tidak hanya untuk menghasilkan hanya satu instance saja; itu gratis untuk menghasilkan satu contoh dari itu dalam penyimpanan read-only.

Jadi, Anda harus menggunakan static constexprcontoh Anda.

Namun, ada satu kasus di mana Anda tidak ingin menggunakannya static constexpr. Kecuali constexprobjek yang dideklarasikan digunakan atau dideklarasikan ODRstatic , kompiler bebas untuk tidak memasukkannya sama sekali. Itu cukup berguna, karena memungkinkan penggunaan constexprarray sementara waktu kompilasi tanpa mencemari program yang dikompilasi dengan byte yang tidak perlu. Dalam hal ini, Anda jelas tidak ingin menggunakan static, karena statickemungkinan akan memaksa objek ada saat runtime.

rici
sumber
2
@AndrewLazarus, Anda tidak dapat membuang constdari constobjek, hanya dari const X*yang menunjuk ke suatu X. Tapi bukan itu intinya; intinya adalah bahwa objek otomatis tidak dapat memiliki alamat statis. Seperti yang saya katakan, constexprberhenti menjadi bermakna setelah kompilasi selesai, jadi tidak ada yang dibuang (dan sangat mungkin tidak ada sama sekali, karena objek tersebut bahkan tidak dijamin ada saat runtime.)
rici
17
Saya merasa tidak hanya apakah jawaban ini sangat membingungkan tetapi juga bertentangan dengan diri sendiri. Misalnya Anda mengatakan bahwa Anda hampir selalu menginginkan staticdan constexprtetapi jelaskan bahwa mereka ortogonal dan mandiri, melakukan hal-hal yang berbeda. Anda kemudian menyebutkan alasan untuk TIDAK menggabungkan keduanya karena akan mengabaikan penggunaan ODR (yang tampaknya berguna). Oh dan saya masih tidak melihat mengapa statis harus digunakan dengan constexpr karena statis untuk hal-hal runtime. Anda tidak pernah menjelaskan mengapa statis dengan constexpr itu penting.
void.pointer
2
@ void.pointer: Anda benar tentang paragraf terakhir. Saya mengubah intro. Saya pikir saya telah menjelaskan pentingnya static constexpr(itu mencegah array konstan harus diciptakan kembali pada setiap panggilan fungsi), tetapi saya mengubah beberapa kata yang mungkin membuatnya lebih jelas. Terima kasih.
rici
8
Mungkin juga berguna untuk menyebutkan konstanta waktu kompilasi vs konstanta runtime. Dengan kata lain, jika constexprvariabel konstan hanya digunakan dalam konteks waktu kompilasi dan tidak pernah diperlukan saat runtime, maka statictidak masuk akal, karena pada titik Anda sampai ke runtime, nilainya telah secara efektif "digariskan". Namun, jika constexprdigunakan dalam konteks runtime (dengan kata lain, yang constexprperlu dikonversi constsecara implisit, dan tersedia dengan alamat fisik untuk kode runtime) itu akan ingin staticmemastikan kepatuhan ODR, dll. Itulah pemahaman saya, setidaknya.
void.pointer
3
Contoh untuk komentar terakhir saya: static constexpr int foo = 100;. Tidak ada alasan mengapa kompiler tidak dapat menggantikan penggunaan di foomana-mana dengan literal 100, kecuali kode melakukan sesuatu seperti &foo. Jadi staticpada footidak memiliki kegunaan dalam kasus ini karena footidak ada pada saat runtime. Sekali lagi semuanya sampai ke kompiler.
void.pointer
10

Selain jawaban yang diberikan, perlu dicatat bahwa kompiler tidak diharuskan untuk menginisialisasi constexprvariabel pada waktu kompilasi, mengetahui bahwa perbedaan antara constexprdan static constexprapakah untuk menggunakan static constexprAnda memastikan variabel diinisialisasi hanya sekali.

Kode berikut menunjukkan bagaimana constexprvariabel diinisialisasi beberapa kali (dengan nilai yang sama), sedangkan static constexprpasti diinisialisasi hanya sekali.

Selain itu, kode membandingkan keuntungan constexprmelawan constdalam kombinasi dengan 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;
}

Output program yang mungkin:

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

Seperti yang Anda lihat sendiri, constexprbeberapa kali tidak staticdiinisiasi (alamat tidak sama) sementara kata kunci memastikan bahwa inisialisasi dilakukan hanya sekali.

metablaster
sumber
tidak bisakah kita menggunakan constexpr const short constexpr_shortuntuk memberikan kesalahan jika constexpr_short diinisialisasi lagi
akhileshzmishra
sintaks Anda constexpr consttidak masuk akal karena constexprsudah ada const, menambahkan constsekali atau beberapa kali diabaikan oleh kompiler. Anda mencoba menangkap kesalahan tetapi ini bukan kesalahan, itulah cara kerja kebanyakan kompiler.
metablaster