Kapan tanda kurung tambahan berpengaruh, selain pada prioritas operator?

91

Tanda kurung dalam C ++ digunakan di banyak tempat: misalnya dalam pemanggilan fungsi dan ekspresi pengelompokan untuk mengesampingkan prioritas operator. Terlepas dari tanda kurung tambahan ilegal (seperti di sekitar daftar argumen panggilan fungsi), aturan umum -tetapi tidak mutlak- dari C ++ adalah bahwa tanda kurung tambahan tidak pernah merugikan :

5.1 Ekspresi utama [expr.prim]

5.1.1 Umum [expr.prim.general]

6 Ekspresi dalam kurung adalah ekspresi primer yang jenis dan nilainya identik dengan ekspresi tertutup. Adanya tanda kurung tidak mempengaruhi apakah ekspresi tersebut adalah nilai l. Ekspresi dalam tanda kurung dapat digunakan dalam konteks yang sama persis dengan yang di mana ekspresi tertutup dapat digunakan, dan dengan arti yang sama, kecuali ditunjukkan lain .

Pertanyaan : dalam konteks apa tanda kurung ekstra mengubah arti program C ++, selain mengesampingkan prioritas operator dasar?

CATATAN : Saya menganggap pembatasan sintaks pointer-ke-anggota&qualified-id tanpa tanda kurung berada di luar ruang lingkup karena membatasi sintaks daripada mengizinkan dua sintaks dengan arti yang berbeda. Demikian pula, penggunaan tanda kurung di dalam definisi makro preprocessor juga melindungi dari prioritas operator yang tidak diinginkan.

TemplateRex
sumber
"Saya menganggap resolusi & (id yang memenuhi syarat) untuk pointer-ke-anggota sebagai aplikasi prioritas operator." -- Mengapa demikian? Jika Anda menghilangkan tanda kurung di &(C::f), operan dari &masih C::f, bukan?
@hvd expr.unary.op/4: Sebuah pointer ke anggota hanya dibentuk ketika sebuah eksplisit &digunakan dan operannya adalah id-yang memenuhi syarat yang tidak diapit tanda kurung.
TemplateRex
Benar, jadi apa hubungannya dengan prioritas operator? (Tidak apa-apa, pertanyaan yang Anda edit menjelaskannya.)
@hvd diperbarui, saya membingungkan RHS dengan LHS dalam Tanya Jawab ini , dan di sana tanda kurung digunakan untuk mengesampingkan pemanggilan fungsi di ()atas pemilih pointer-ke-anggota::*
TemplateRex
1
Saya pikir Anda harus sedikit lebih tepat tentang kasus mana yang perlu dipertimbangkan. Misalnya tanda kurung di sekitar nama tipe untuk menjadikannya operator cor gaya-C (apa pun konteksnya) tidak membuat ekspresi kurung sama sekali. Di sisi lain saya akan mengatakan secara teknis kondisi setelah if atau while adalah ekspresi dalam tanda kurung, tetapi karena tanda kurung adalah bagian dari sintaks di sini, mereka tidak boleh dipertimbangkan. IMO juga tidak boleh menjadi kasus apa pun, di mana tanpa tanda kurung ekspresi tidak lagi diurai sebagai satu unit, apakah prioritas operator terlibat atau tidak.
Marc van Leeuwen

Jawaban:

113

TL; DR

Tanda kurung tambahan mengubah arti program C ++ dalam konteks berikut:

  • mencegah pencarian nama yang bergantung pada argumen
  • mengaktifkan operator koma dalam konteks daftar
  • resolusi ambiguitas dari parsing yang mengganggu
  • menyimpulkan referensi dalam decltypeekspresi
  • mencegah kesalahan makro preprocessor

Mencegah pencarian nama yang bergantung pada argumen

Seperti yang dijelaskan dalam Lampiran A Standar, post-fix expressionbentuk (expression)a adalah a primary expression, tetapi bukan an id-expression, dan oleh karena itu bukan an unqualified-id. Ini berarti bahwa pencarian nama yang bergantung pada argumen dicegah dalam pemanggilan fungsi dari formulir (fun)(arg)dibandingkan dengan formulir konvensional fun(arg).

3.4.2 Pencarian nama yang bergantung pada argumen [basic.lookup.argdep]

1 Ketika ekspresi-postfix dalam panggilan fungsi (5.2.2) adalah id tanpa kualifikasi , ruang nama lain yang tidak dipertimbangkan selama pencarian biasa yang tidak memenuhi syarat (3.4.1) dapat dicari, dan di ruang nama tersebut, fungsi teman lingkup namespace atau deklarasi template fungsi (11.3) yang tidak terlihat dapat ditemukan. Modifikasi pada pencarian ini bergantung pada tipe argumen (dan untuk template template argumen, namespace argumen template). [Contoh:

namespace N {
    struct S { };
    void f(S);
}

void g() {
    N::S s;
    f(s);   // OK: calls N::f
    (f)(s); // error: N::f not considered; parentheses
            // prevent argument-dependent lookup
}

—Dan contoh]

Mengaktifkan operator koma dalam konteks daftar

Operator koma memiliki arti khusus di sebagian besar konteks seperti daftar (argumen fungsi dan templat, daftar penginisialisasi, dll.). Tanda kurung formulir a, (b, c), ddalam konteks seperti itu dapat mengaktifkan operator koma dibandingkan dengan formulir biasa di a, b, c, dmana operator koma tidak berlaku.

5.18 Operator koma [expr.comma]

2 Dalam konteks di mana koma diberi arti khusus, [Contoh: dalam daftar argumen ke fungsi (5.2.2) dan daftar penginisialisasi (8.5) —dan contoh] operator koma seperti yang dijelaskan dalam Klausul 5 hanya dapat muncul dalam tanda kurung. [Contoh:

f(a, (t=3, t+2), c);

memiliki tiga argumen, yang kedua memiliki nilai 5. —sudah contoh]

Resolusi ambiguitas dari parsing yang mengganggu

Kompatibilitas mundur dengan C dan sintaks deklarasi fungsi rahasia dapat menyebabkan ambiguitas penguraian yang mengejutkan, yang dikenal sebagai penguraian menjengkelkan. Pada dasarnya, apa pun yang dapat diuraikan sebagai deklarasi akan diuraikan sebagai satu deklarasi , meskipun parse yang bersaing juga akan diterapkan.

6.8 Resolusi ambiguitas [stmt.ambig]

1 Ada ambiguitas dalam tata bahasa yang melibatkan pernyataan-ekspresi dan deklarasi : Pernyataan-ekspresi dengan konversi tipe eksplisit gaya fungsi (5.2.3) sebagai subekspresi paling kiri dapat dibedakan dari deklarasi di mana deklarator pertama dimulai dengan a ( . dalam kasus-kasus pernyataan itu adalah sebuah deklarasi .

8.2 Resolusi ambiguitas [dcl.ambig.res]

1 Ambiguitas yang timbul dari kesamaan antara cor gaya-fungsi dan deklarasi yang disebutkan dalam 6.8 juga dapat terjadi dalam konteks deklarasi . Dalam konteks itu, pilihannya adalah antara deklarasi fungsi dengan sekumpulan tanda kurung yang berlebihan di sekitar nama parameter dan deklarasi objek dengan cast gaya fungsi sebagai penginisialisasi. Sama seperti ambiguitas yang disebutkan dalam 6.8, penyelesaiannya adalah dengan mempertimbangkan setiap konstruksi yang mungkin merupakan deklarasi sebagai deklarasi . [Catatan: Deklarasi dapat secara eksplisit disingkirkan oleh cast bergaya nonfungsi, dengan = untuk menunjukkan inisialisasi atau dengan menghapus tanda kurung yang berlebihan di sekitar nama parameter. —Kirim catatan] [Contoh:

struct S {
    S(int);
};

void foo(double a) {
    S w(int(a));  // function declaration
    S x(int());   // function declaration
    S y((int)a);  // object declaration
    S z = int(a); // object declaration
}

—Dan contoh]

Contoh terkenal dari ini adalah Most Vexing Parse , nama yang dipopulerkan oleh Scott Meyers di Item 6 dari buku Effective STL-nya :

ifstream dataFile("ints.dat");
list<int> data(istream_iterator<int>(dataFile), // warning! this doesn't do
               istream_iterator<int>());        // what you think it does

Ini mendeklarasikan sebuah fungsi data, yang tipe kembaliannya adalah list<int>. Data fungsi mengambil dua parameter:

  • Parameter pertama diberi nama dataFile. Jenisnya adalah istream_iterator<int>. Tanda kurung di sekitarnya dataFiletidak berguna dan diabaikan.
  • Parameter kedua tidak memiliki nama. Jenisnya adalah pointer untuk berfungsi tidak mengambil apa pun dan mengembalikan file istream_iterator<int>.

Menempatkan tanda kurung tambahan di sekitar argumen fungsi pertama (tanda kurung di sekitar argumen kedua adalah ilegal) akan menyelesaikan ambiguitas

list<int> data((istream_iterator<int>(dataFile)), // note new parens
                istream_iterator<int>());          // around first argument
                                                  // to list's constructor

C ++ 11 memiliki sintaks brace-initializer yang memungkinkan untuk mengesampingkan masalah penguraian seperti itu dalam banyak konteks.

Mengurangi referensi dalam decltypeekspresi

Berbeda dengan autodeduksi tipe, decltypememungkinkan referensi (referensi nilai l dan nilai r) untuk disimpulkan. Aturan membedakan antara decltype(e)dan decltype((e))ekspresi:

7.1.6.2 Penentu tipe sederhana [dcl.type.simple]

4 Untuk ekspresi e, tipe yang dilambangkan dengandecltype(e) didefinisikan sebagai berikut:

- jika eadalah ekspresi-id yang tidak diberi tanda kurung atau akses anggota kelas yang tidak diberi tanda kurung (5.2.5), decltype(e)adalah jenis entitas yang dinamai oleh e. Jika tidak ada entitas seperti itu, atau jika emenamai sekumpulan fungsi yang kelebihan beban, programnya salah format;

- sebaliknya, jika enilai x, decltype(e)adalah T&&, di mana Tjenis e;

- sebaliknya, jika enilai l, decltype(e)adalah T&, di mana Tjenis e;

- jika tidak, decltype(e)adalah tipe e.

Operand dari penentu jenis deklarasi adalah operan yang tidak dievaluasi (Klausul 5). [Contoh:

const int&& foo();
int i;
struct A { double x; };
const A* a = new A();
decltype(foo()) x1 = 0;   // type is const int&&
decltype(i) x2;           // type is int
decltype(a->x) x3;        // type is double
decltype((a->x)) x4 = x3; // type is const double&

—Dan contoh] [Catatan: Aturan untuk menentukan tipe yang melibatkan decltype(auto)ditentukan dalam 7.1.6.4. —Kirim catatan]

Aturan untuk decltype(auto)memiliki arti yang sama untuk tanda kurung tambahan di kanan atas ekspresi inisialisasi. Berikut adalah contoh dari C ++ FAQ dan Q&A terkait ini

decltype(auto) look_up_a_string_1() { auto str = lookup1(); return str; }  //A
decltype(auto) look_up_a_string_2() { auto str = lookup1(); return(str); } //B

Pengembalian pertama string, pengembalian kedua string &, yang merupakan referensi ke variabel lokal str.

Mencegah kesalahan terkait makro praprosesor

Ada sejumlah kehalusan dengan makro praprosesor dalam interaksinya dengan bahasa C ++ yang sesuai, yang paling umum tercantum di bawah ini

  • menggunakan tanda kurung di sekitar parameter makro di dalam definisi makro #define TIMES(A, B) (A) * (B);untuk menghindari prioritas operator yang tidak diinginkan (misalnya di TIMES(1 + 2, 2 + 1)mana menghasilkan 9 tetapi akan menghasilkan 6 tanpa tanda kurung di sekitarnya (A)dan(B)
  • menggunakan tanda kurung di sekitar argumen makro yang memiliki koma di dalamnya: assert((std::is_same<int, int>::value));yang tidak dapat dikompilasi
  • menggunakan tanda kurung di sekitar fungsi untuk melindungi dari ekspansi makro di header yang disertakan: (min)(a, b)(dengan efek samping yang tidak diinginkan juga menonaktifkan ADL)
TemplateRex
sumber
7
Tidak benar-benar mengubah arti program, tetapi praktik terbaik dan memengaruhi peringatan yang dikeluarkan oleh kompilator: tanda kurung tambahan harus digunakan di if/ whilejika ekspresi adalah tugas. Misalnya if (a = b)- peringatan (maksud Anda ==?), Sementara if ((a = b))- tidak ada peringatan.
Csq
@Csq terima kasih, observasi yang bagus, tapi itu adalah peringatan oleh kompilator tertentu dan tidak diamanatkan oleh Standard. Menurut saya, hal itu tidak sesuai dengan sifat pengacara bahasa dari Tanya Jawab ini.
TemplateRex
Apakah (min)(a, b)(dengan MACRO jahat min(A, B)) adalah bagian dari pencegahan pencarian nama yang bergantung pada argumen?
Jarod42
@ Jarod42 Saya rasa begitu, tapi mari kita pertimbangkan makro jahat ini dan lainnya berada di luar cakupan pertanyaan :-)
TemplateRex
5
@JamesKanze: Perhatikan bahwa OP dan TemplateRex adalah orang yang sama ^ _ ^
Jarod42
4

Secara umum, dalam bahasa pemrograman, tanda kurung "ekstra" menyiratkan bahwa mereka tidak mengubah urutan atau makna penguraian sintaksis. Mereka ditambahkan untuk memperjelas urutan (prioritas operator) untuk kepentingan orang-orang yang membaca kode, dan satu-satunya efek mereka adalah sedikit memperlambat proses kompilasi, dan mengurangi kesalahan manusia dalam memahami kode (mungkin mempercepat proses pengembangan secara keseluruhan ).

Jika sekumpulan tanda kurung benar-benar mengubah cara ekspresi diuraikan, maka menurut definisi tidak tambahan. Tanda kurung yang mengubah penguraian ilegal / tidak valid menjadi penguraian legal bukanlah "ekstra", meskipun itu mungkin menunjukkan desain bahasa yang buruk.

Phil Perry
sumber
2
persis, dan ini adalah aturan umum dalam C ++ juga (lihat kutipan Standar dalam pertanyaan), kecuali jika dinyatakan lain . Untuk menunjukkan "kelemahan" ini adalah tujuan dari Tanya Jawab ini.
TemplateRex