Upaya saya pada inisialisasi nilai ditafsirkan sebagai deklarasi fungsi, dan mengapa A tidak (()); menyelesaikannya?


158

Di antara banyak hal yang Stack Overflow telah ajarkan kepada saya adalah apa yang dikenal sebagai "parse yang paling menjengkelkan", yang secara klasik ditunjukkan dengan garis seperti

A a(B()); //declares a function

Sementara ini, untuk sebagian besar, secara intuitif tampak sebagai deklarasi objek abertipe A, mengambil Bobjek sementara sebagai parameter konstruktor, sebenarnya ini merupakan deklarasi fungsi yang amengembalikan A, mengambil pointer ke fungsi yang mengembalikan Bdan itu sendiri tidak mengambil parameter . Begitu pula dengan garis

A a(); //declares a function

juga jatuh di bawah kategori yang sama, karena alih-alih objek, itu menyatakan fungsi. Sekarang, dalam kasus pertama, solusi yang biasa untuk masalah ini adalah dengan menambahkan set kurung / kurung tambahan di sekitar B(), karena kompiler kemudian akan menafsirkannya sebagai deklarasi objek

A a((B())); //declares an object

Namun, dalam kasus kedua, melakukan hal yang sama mengarah ke kesalahan kompilasi

A a(()); //compile error

Pertanyaan saya adalah, mengapa? Ya, saya sangat menyadari bahwa 'solusi' yang benar adalah mengubahnya A a;, tetapi saya ingin tahu apa yang dilakukan ekstra ()untuk kompiler pada contoh pertama yang kemudian tidak berfungsi saat mendaftar ulang di contoh kedua. Apakah A a((B()));solusinya pengecualian khusus ditulis ke dalam standar?


20
(B())hanyalah ekspresi C ++, tidak lebih. Ini bukan pengecualian. Satu-satunya perbedaan yang dibuatnya adalah bahwa tidak mungkin itu dapat diuraikan sebagai tipe, dan karenanya tidak.
Pavel Minaev

12
Hal ini juga harus dicatat bahwa kasus kedua, A a();adalah tidak dari kategori yang sama. Untuk kompiler , tidak pernah ada cara lain untuk menguraikannya: Penginisialisasi di tempat itu tidak pernah terdiri dari tanda kurung kosong, jadi ini selalu merupakan deklarasi fungsi.
Johannes Schaub - litb

11
Poin bagus litb adalah yang halus namun penting dan patut ditekankan - alasan ambiguitas ada dalam deklarasi ini 'A (B ())' adalah dalam penguraian dari 'B ()' -> dapat berupa ekspresi & deklarasi & kompiler harus 'memilih' menyatakan lebih dari expr - jadi jika B () adalah menyatakan maka 'a' hanya bisa merupakan func mendeklarasikan (bukan deklarasi variabel). Jika '()' diizinkan menjadi inisialisasi 'A a ()' akan ambigu - tetapi tidak expr vs. decl, tetapi var decl vs func decl - tidak ada aturan untuk memilih satu mendeklarasikan daripada yang lain - dan sebagainya '() 'Tidak diizinkan sebagai inisialisasi di sini - dan ambiguitas tidak meningkat.
Faisal Vali

6
A a();adalah bukan contoh yang paling menjengkelkan parse . Ini hanyalah deklarasi fungsi, seperti halnya di C.
Pete Becker

2
"'solusi' yang benar adalah mengubahnya menjadi A a;" salah. Itu tidak akan memberi Anda inisialisasi jenis POD. Untuk mendapatkan inisialisasi tulis A a{};.
Ceria dan hth. - Alf

Jawaban:


70

Tidak ada jawaban tercerahkan, itu hanya karena itu tidak didefinisikan sebagai sintaks yang valid oleh bahasa C ++ ... Jadi, definisi bahasa.

Jika Anda memiliki ekspresi di dalamnya maka itu valid. Sebagai contoh:

 ((0));//compiles

Bahkan lebih sederhana: karena (x)merupakan ekspresi C ++ yang valid, sedangkan ()tidak.

Untuk mempelajari lebih lanjut tentang bagaimana bahasa didefinisikan, dan bagaimana kompiler bekerja, Anda harus belajar tentang teori bahasa formal atau lebih khusus lagi Tata Bahasa Bebas Konteks (CFG) dan materi terkait seperti mesin keadaan terbatas. Jika Anda tertarik meskipun halaman wikipedia tidak cukup, Anda harus mendapatkan buku.


45
Bahkan lebih sederhana: karena (x)merupakan ekspresi C ++ yang valid, sedangkan ()tidak.
Pavel Minaev

Saya telah menerima jawaban ini, selain itu komentar Pavel untuk pertanyaan awal saya banyak membantu saya
GRB


29

Deklarator fungsi C

Pertama-tama, ada C. Dalam C, A a()adalah deklarasi fungsi. Misalnya, putcharmemiliki deklarasi berikut. Biasanya, deklarasi semacam itu disimpan dalam file header, namun tidak ada yang menghentikan Anda dari menulisnya secara manual, jika Anda tahu bagaimana deklarasi fungsi itu terlihat. Nama argumen adalah opsional dalam deklarasi, jadi saya menghilangkannya dalam contoh ini.

int putchar(int);

Ini memungkinkan Anda untuk menulis kode seperti ini.

int puts(const char *);
int main() {
    puts("Hello, world!");
}

C juga memungkinkan Anda untuk mendefinisikan fungsi yang mengambil fungsi sebagai argumen, dengan sintaks yang dapat dibaca yang terlihat seperti panggilan fungsi (well, ini dapat dibaca, selama Anda tidak akan mengembalikan pointer ke fungsi).

#include <stdio.h>

int eighty_four() {
    return 84;
}

int output_result(int callback()) {
    printf("Returned: %d\n", callback());
    return 0;
}

int main() {
    return output_result(eighty_four);
}

Seperti yang saya sebutkan, C memungkinkan menghilangkan nama argumen dalam file header, oleh karena itu output_resultakan terlihat seperti ini di file header.

int output_result(int());

Satu argumen dalam konstruktor

Apakah kamu tidak mengenali yang itu? Baiklah, izinkan saya mengingatkan Anda.

A a(B());

Yap, itu deklarasi fungsi yang persis sama. Aadalah int, aadalah output_result, dan Bsedang int.

Anda dapat dengan mudah melihat konflik C dengan fitur baru C ++. Tepatnya, konstruktor menjadi nama kelas dan tanda kurung, dan sintaks deklarasi alternatif dengan ()alih - alih =. Secara desain, C ++ mencoba untuk kompatibel dengan kode C, dan karena itu harus berurusan dengan kasus ini - bahkan jika praktis tidak ada yang peduli. Oleh karena itu, fitur C lama memiliki prioritas di atas fitur C ++ baru. Tata bahasa deklarasi mencoba mencocokkan nama sebagai fungsi, sebelum kembali ke sintaks baru dengan() jika gagal.

Jika salah satu fitur tersebut tidak ada, atau memiliki sintaks yang berbeda (seperti {}dalam C ++ 11), masalah ini tidak akan pernah terjadi untuk sintaksis dengan satu argumen.

Sekarang Anda mungkin bertanya mengapa itu A a((B()))berhasil. Baiklah, mari kita nyatakan output_resultdengan tanda kurung yang tidak berguna.

int output_result((int()));

Itu tidak akan berhasil. Tata bahasanya membutuhkan variabel untuk tidak berada dalam tanda kurung.

<stdin>:1:19: error: expected declaration specifiers or ‘...’ before ‘(’ token

Namun, C ++ mengharapkan ekspresi standar di sini. Di C ++, Anda dapat menulis kode berikut.

int value = int();

Dan kode berikut.

int value = ((((int()))));

C ++ mengharapkan ekspresi di dalam tanda kurung di dalam menjadi ... well ... ekspresi, sebagai lawan dari tipe C mengharapkan. Kurung tidak ada artinya di sini. Namun, dengan menyisipkan tanda kurung yang tidak berguna, deklarasi fungsi C tidak cocok, dan sintaks baru dapat dicocokkan dengan benar (yang hanya mengharapkan ekspresi, seperti 2 + 2).

Lebih banyak argumen dalam konstruktor

Tentunya satu argumen itu bagus, tapi bagaimana dengan dua? Bukan karena konstruktor mungkin hanya memiliki satu argumen. Salah satu kelas bawaan yang mengambil dua argumen adalahstd::string

std::string hundred_dots(100, '.');

Ini semua baik dan baik-baik saja (secara teknis, itu akan sangat mengurai jika dituliskan std::string wat(int(), char()), tetapi mari kita jujur ​​- siapa yang akan menulis itu? Tapi mari kita asumsikan kode ini memiliki masalah yang menjengkelkan. Anda akan menganggap bahwa Anda harus meletakkan semuanya dalam tanda kurung.

std::string hundred_dots((100, '.'));

Tidak juga.

<stdin>:2:36: error: invalid conversion from char to const char*’ [-fpermissive]
In file included from /usr/include/c++/4.8/string:53:0,
                 from <stdin>:1:
/usr/include/c++/4.8/bits/basic_string.tcc:212:5: error:   initializing argument 1 of std::basic_string<_CharT, _Traits, _Alloc>::basic_string(const _CharT*, const _Alloc&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]’ [-fpermissive]
     basic_string<_CharT, _Traits, _Alloc>::
     ^

Saya tidak yakin mengapa g ++ mencoba untuk mengkonversi charke const char *. Either way, konstruktor dipanggil hanya dengan satu nilai tipe char. Tidak ada overload yang memiliki satu argumen tipe char, oleh karena itu kompiler bingung. Anda mungkin bertanya - mengapa argumennya bertipe char?

(100, '.')

Iya, , ini operator koma. Operator koma mengambil dua argumen, dan memberikan argumen sisi kanan. Itu tidak benar-benar berguna, tetapi itu sesuatu yang harus diketahui untuk penjelasan saya.

Sebagai gantinya, untuk memecahkan parse yang paling menjengkelkan, kode berikut ini diperlukan.

std::string hundred_dots((100), ('.'));

Argumennya dalam tanda kurung, bukan seluruh ekspresi. Faktanya, hanya satu ekspresi yang perlu di dalam tanda kurung, karena cukup untuk sedikit keluar dari tata bahasa C untuk menggunakan fitur C ++. Hal-hal membawa kita ke titik nol argumen.

Tidak ada argumen dalam konstruktor

Anda mungkin telah memperhatikan eighty_fourfungsi dalam penjelasan saya.

int eighty_four();

Ya, ini juga dipengaruhi oleh parse yang paling menjengkelkan. Ini adalah definisi yang valid, dan kemungkinan besar Anda telah melihat jika Anda membuat file header (dan Anda harus). Menambahkan tanda kurung tidak memperbaikinya.

int eighty_four(());

Kenapa begitu? Ya, ()itu bukan ekspresi. Di C ++, Anda harus meletakkan ekspresi di antara tanda kurung. Anda tidak dapat menulis auto value = ()dalam C ++, karena ()tidak berarti apa-apa (dan bahkan jika itu, seperti tuple kosong (lihat Python), itu akan menjadi satu argumen, bukan nol). Secara praktis itu berarti Anda tidak dapat menggunakan sintaks steno tanpa menggunakan sintaks C ++ 11 {}, karena tidak ada ekspresi untuk dimasukkan dalam tanda kurung, dan tata bahasa C untuk deklarasi fungsi akan selalu berlaku.


12

Anda malah bisa

A a(());

menggunakan

A a=A();

32
'Solusi yang lebih baik' tidak setara. int a = int();menginisialisasi adengan 0, int a;meninggalkan adiinisialisasi. Solusi yang benar adalah digunakan A a = {};untuk agregat, A a;ketika inisialisasi default melakukan apa yang Anda inginkan, dan A a = A();dalam semua kasus lainnya - atau cukup gunakan A a = A();secara konsisten. Di C ++ 11, cukup gunakanA a {};
Richard Smith

6

Parens terdalam dalam contoh Anda akan menjadi ekspresi, dan dalam C + + tata bahasa mendefinisikan suatu expressionmenjadi assignment-expressionatau yang lain expressiondiikuti oleh koma dan lainnyaassignment-expression (Lampiran A.4 - Ringkasan / Ekspresi Grammar).

Tata bahasa selanjutnya mendefinisikan suatu assignment-expression sebagai salah satu dari beberapa jenis ekspresi lainnya, tidak ada yang bisa apa-apa (atau hanya spasi putih).

Jadi alasan Anda tidak dapat memiliki A a(())hanya karena tata bahasa tidak mengizinkannya. Namun, saya tidak bisa menjawab mengapa orang-orang yang membuat C ++ tidak mengizinkan penggunaan parens kosong ini sebagai semacam kasus khusus - saya kira mereka lebih suka tidak memasukkan kasus khusus seperti itu jika ada alternatif yang masuk akal.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.