Menghitung panjang string C pada waktu kompilasi. Apakah ini benar-benar sebuah constexpr?

94

Saya mencoba menghitung panjang string literal pada waktu kompilasi. Untuk melakukannya, saya menggunakan kode berikut:

#include <cstdio>

int constexpr length(const char* str)
{
    return *str ? 1 + length(str + 1) : 0;
}

int main()
{
    printf("%d %d", length("abcd"), length("abcdefgh"));
}

Semuanya bekerja seperti yang diharapkan, program mencetak 4 dan 8. Kode assembly yang dihasilkan oleh clang menunjukkan bahwa hasil dihitung pada waktu kompilasi:

0x100000f5e:  leaq   0x35(%rip), %rdi          ; "%d %d"
0x100000f65:  movl   $0x4, %esi
0x100000f6a:  movl   $0x8, %edx
0x100000f6f:  xorl   %eax, %eax
0x100000f71:  callq  0x100000f7a               ; symbol stub for: printf

Pertanyaan saya: apakah dijamin oleh standar bahwa lengthfungsi akan dievaluasi waktu kompilasi?

Jika ini benar, pintu untuk komputasi literal string waktu kompilasi baru saja dibuka untuk saya ... misalnya saya dapat menghitung hash pada waktu kompilasi dan banyak lagi ...

Mircea Ispas
sumber
3
Selama parameter adalah ekspresi konstan, itu harus.
chris
1
@chris Apakah ada jaminan bahwa sesuatu yang dapat berupa ekspresi konstan harus dievaluasi pada waktu kompilasi saat digunakan dalam konteks yang tidak memerlukan ekspresi konstan?
TC
12
BTW, termasuk <cstdio>dan kemudian panggilan ::printftidak portabel. Standar hanya perlu <cstdio>menyediakan std::printf.
Ben Voigt
1
@BenVoigt Ok, terima kasih telah menunjukkannya :) Awalnya saya menggunakan std :: cout, tetapi kode yang dihasilkan cukup besar untuk menemukan nilai sebenarnya :)
Mircea Ispas
3
@Felics Saya sering menggunakan godbolt saat menjawab pertanyaan yang berhubungan dengan pengoptimalan dan penggunaan printfdapat menyebabkan lebih sedikit kode untuk ditangani.
Shafik Yaghmour

Jawaban:

76

Ekspresi konstan tidak dijamin akan dievaluasi pada waktu kompilasi, kami hanya memiliki kutipan non-normatif dari draf bagian standar C ++ 5.19 Ekspresi konstan yang mengatakan ini:

[...]> [Catatan: Ekspresi konstan dapat dievaluasi selama penerjemahan. — catatan akhir]

Anda dapat menetapkan hasilnya ke constexprvariabel untuk memastikannya dievaluasi pada waktu kompilasi, kita dapat melihat ini dari referensi C ++ 11 Bjarne Stroustrup yang mengatakan ( penekanan saya ):

Selain dapat mengevaluasi ekspresi pada waktu kompilasi, kami ingin meminta ekspresi untuk dievaluasi pada waktu kompilasi; constexpr di depan definisi variabel melakukan itu (dan menyiratkan const):

Sebagai contoh:

constexpr int len1 = length("abcd") ;

Bjarne Stroustrup memberikan ringkasan kapan kami dapat memastikan evaluasi waktu kompilasi dalam entri blog isocpp ini dan mengatakan:

[...] Jawaban yang benar - seperti yang dinyatakan oleh Herb - adalah bahwa menurut standar fungsi constexpr dapat dievaluasi pada waktu kompilator atau waktu berjalan kecuali jika digunakan sebagai ekspresi konstan, dalam hal ini harus dievaluasi pada saat kompilasi -waktu. Untuk menjamin evaluasi waktu kompilasi, kita harus menggunakannya di mana ekspresi konstan diperlukan (misalnya, sebagai ikatan array atau sebagai label case) atau menggunakannya untuk menginisialisasi konsteks. Saya berharap bahwa tidak ada kompiler yang menghargai diri sendiri yang akan melewatkan peluang pengoptimalan untuk melakukan apa yang awalnya saya katakan: "Fungsi constexpr dievaluasi pada waktu kompilasi jika semua argumennya adalah ekspresi konstan."

Jadi ini menguraikan dua kasus yang harus dievaluasi pada waktu kompilasi:

  1. Gunakan di mana ekspresi konstan diperlukan, ini akan tampak di mana saja dalam standar draf tempat frase shall be ... converted constant expressionatau shall be ... constant expressiondigunakan, seperti terikat larik.
  2. Gunakan itu untuk menginisialisasi constexprseperti yang saya uraikan di atas.
Shafik Yaghmour
sumber
4
Yang mengatakan, pada prinsipnya kompilator berhak melihat objek dengan internal atau tanpa tautan constexpr int x = 5;, perhatikan bahwa itu tidak memerlukan nilai pada waktu kompilasi (dengan asumsi itu tidak digunakan sebagai parameter templat atau yang lainnya) dan benar-benar memancarkan kode yang menghitung nilai awal saat runtime menggunakan 5 nilai langsung dari 1 dan 4 operasi tambahan. Contoh yang lebih realistis: kompilator mungkin mencapai batas rekursi dan menunda komputasi hingga waktu proses. Kecuali jika Anda melakukan sesuatu yang memaksa kompilator untuk benar-benar menggunakan nilai, "dijamin akan dievaluasi pada waktu kompilasi" adalah masalah QOI.
Steve Jessop
@SteveJessop Bjarne tampaknya menggunakan konsep yang tidak memiliki analog yang dapat saya temukan dalam draf standar yang digunakan sebagai sarana ekspresi konstan yang dievaluasi pada terjemahan. Jadi tampaknya standar tersebut tidak secara eksplisit menyatakan apa yang dia katakan, jadi saya cenderung setuju dengan Anda. Meskipun Bjarne dan Herb tampaknya setuju tentang hal ini, yang dapat mengindikasikan bahwa itu tidak ditentukan secara spesifik.
Shafik Yaghmour
2
Saya pikir mereka berdua hanya mempertimbangkan "kompiler yang menghargai diri sendiri", sebagai lawan dari kompiler yang sesuai dengan standar tetapi dengan sengaja menghalangi hipotesis saya. Ini berguna sebagai sarana penalaran tentang apa yang sebenarnya dijamin oleh standar , dan tidak banyak lagi ;-)
Steve Jessop
3
@SteveJessop Kompiler yang sengaja menghalangi, seperti Hell ++ yang terkenal (dan sayangnya tidak ada). Hal seperti itu sebenarnya bagus untuk menguji kesesuaian / portabilitas.
Angew tidak lagi bangga dengan SO
Di bawah aturan as-if, bahkan menggunakan nilai sebagai konstanta waktu kompilasi saja tidak cukup: kompilator bebas untuk mengirimkan salinan sumber Anda dan mengkompilasinya kembali pada waktu proses, atau melakukan kalkulasi untuk menentukan jenis a variabel, atau hanya menjalankan kembali constexprperhitungan Anda dari kejahatan belaka. Bahkan Anda bebas menunggu 1 detik per karakter di baris sumber tertentu, atau mengambil baris sumber tertentu dan menggunakannya untuk menyemai posisi catur, lalu memainkan kedua sisi untuk menentukan siapa yang menang.
Yakk - Adam Nevraumont
27

Sangat mudah untuk mengetahui apakah panggilan ke suatu constexprfungsi menghasilkan ekspresi konstanta inti atau hanya dioptimalkan:

Gunakan dalam konteks di mana ekspresi konstan diperlukan.

int main()
{
    constexpr int test_const = length("abcd");
    std::array<char,length("abcdefgh")> test_const2;
}
Ben Voigt
sumber
4
... dan kompilasi dengan -pedantic, jika Anda menggunakan gcc. Jika tidak, Anda tidak akan mendapatkan peringatan dan kesalahan
BЈовић
@ BЈовић Atau gunakan dalam konteks di mana GCC tidak memiliki ekstensi yang berpotensi menghalangi, seperti argumen template.
Angew tidak lagi bangga dengan SO
Bukankah peretasan enum lebih andal? Seperti enum { Whatever = length("str") }?
gigi tajam
18
Layak disebutkan adalahstatic_assert(length("str") == 3, "");
chris
8
constexpr auto test = /*...*/;mungkin yang paling umum dan mudah.
TC
19

Sebagai catatan, kompiler modern (seperti gcc-4.x) melakukannya strlenuntuk literal string pada waktu kompilasi karena biasanya didefinisikan sebagai fungsi intrinsik . Tanpa pengoptimalan yang diaktifkan. Meskipun hasilnya bukan konstanta waktu kompilasi.

Misalnya:

printf("%zu\n", strlen("abc"));

Hasil dalam:

movl    $3, %esi    # strlen("abc")
movl    $.LC0, %edi # "%zu\n"
movl    $0, %eax
call    printf
Maxim Egorushkin
sumber
Catatan, ini berfungsi karena strlenmerupakan fungsi -fno-builtins
bawaan
strlenadalah constexpruntuk saya, bahkan dengan -fno-nonansi-builtins(sepertinya -fno-builtinstidak ada lagi di g ++). Saya mengatakan "constexpr", karena saya bisa melakukan ini template<int> void foo();dan foo<strlen("hi")>(); g ++ - 4.8.4
Aaron McDaid
19

Izinkan saya mengusulkan fungsi lain yang menghitung panjang string pada waktu kompilasi tanpa rekursif.

template< size_t N >
constexpr size_t length( char const (&)[N] )
{
  return N-1;
}

Lihat kode contoh ini di ideone .

pengguna2436830
sumber
5
Ini tidak bisa sama dengan strlen karena tertanam '\ 0': strlen ("hi \ 0there")! = Length ("hi \ 0there")
unkulunkulu
Ini adalah cara yang benar, ini adalah contoh di Effective Modern C ++ (jika saya ingat dengan benar). Namun, ada kelas string yang bagus yang seluruhnya constexpr lihat jawaban ini: str_const Scott Schurr , mungkin ini akan lebih berguna (dan lebih sedikit gaya C).
QuantumKarl
@MikeWeir Ops, itu aneh. Berikut berbagai tautan: tautan ke pertanyaan , tautan ke kertas , tautan ke sumber di git
QuantumKarl
sekarang yow lakukan: char temp[256]; sprintf(temp, "%u", 2); if(1 != length(temp)) printf("Your solution doesn't work"); ideone.com/IfKUHV
Pablo Ariel
7

Tidak ada jaminan bahwa suatu constexprfungsi dievaluasi pada waktu kompilasi, meskipun kompilator yang wajar akan melakukannya pada tingkat pengoptimalan yang sesuai diaktifkan. Di sisi lain, parameter template harus dievaluasi pada waktu kompilasi.

Saya menggunakan trik berikut untuk memaksa evaluasi pada waktu kompilasi. Sayangnya ini hanya bekerja dengan nilai integral (yaitu tidak dengan nilai floating point).

template<typename T, T V>
struct static_eval
{
  static constexpr T value = V;
};

Sekarang, jika Anda menulis

if (static_eval<int, length("hello, world")>::value > 7) { ... }

Anda dapat yakin bahwa ifpernyataan tersebut adalah konstanta waktu kompilasi tanpa overhead waktu proses.

5gon12eder
sumber
8
atau cukup gunakan std :: integral_constant <int, length (...)> :: value
Mircea Ispas
1
Contoh ini sedikit tidak berguna karena lenbeing constexprmeans lengthharus dievaluasi pada waktu kompilasi.
chris
@ Chris Saya tidak tahu itu harus menjadi, meskipun saya telah mengamati bahwa adalah dengan kompiler saya.
5gon12eder
Oke, menurut sebagian besar jawaban lain itu harus, jadi saya telah memodifikasi contoh menjadi kurang berarti. Sebenarnya, itu adalah if-kondisi (di mana itu penting kompiler melakukan penghapusan kode mati) yang awalnya saya gunakan triknya.
5gon12eder
1

Penjelasan singkat dari entri Wikipedia tentang ekspresi konstanta yang digeneralisasi :

Penggunaan constexpr pada suatu fungsi menyebabkan beberapa batasan pada fungsi yang dapat dilakukan oleh fungsi tersebut. Pertama, fungsi tersebut harus memiliki tipe pengembalian non-void. Kedua, badan fungsi tidak dapat mendeklarasikan variabel atau mendefinisikan tipe baru. Ketiga, body hanya dapat berisi deklarasi, pernyataan null, dan pernyataan return tunggal. Harus ada nilai argumen sedemikian rupa sehingga, setelah substitusi argumen, ekspresi dalam pernyataan return menghasilkan ekspresi konstan.

Memiliki constexprkata kunci sebelum definisi fungsi menginstruksikan kompilator untuk memeriksa apakah batasan ini terpenuhi. Jika ya, dan fungsinya dipanggil dengan konstanta, nilai yang dikembalikan dijamin konstan dan dengan demikian dapat digunakan di mana pun ekspresi konstan diperlukan.

kaedinger
sumber
Kondisi ini tidak menjamin nilai yang dikembalikan konstan . Misalnya, fungsi tersebut mungkin dipanggil dengan nilai argumen lain.
Ben Voigt
Benar, @BenVoigt. Saya mengeditnya menjadi disebut dengan ekspresi konstan.
kaedinger