Ekstra parantezlerin operatör önceliği dışında ne zaman bir etkisi olur?


92

C ++ 'daki parantezler birçok yerde kullanılır: örneğin, operatör önceliğini geçersiz kılmak için işlev çağrılarında ve ifadeleri gruplandırmada. Geçersiz fazladan parantezlerin (işlev çağrısı bağımsız değişken listeleri etrafındaki gibi) dışında, C ++ 'nın genel -ama mutlak değil- kuralı, ekstra parantezlerin asla zarar vermemesidir :

5.1 Birincil ifadeler [ifade.prim]

5.1.1 Genel [expr.prim.general]

6 Parantezli ifade, türü ve değeri ekteki ifadeninkilerle aynı olan birincil bir ifadedir. Parantezlerin varlığı, ifadenin bir değer olup olmadığını etkilemez. Parantezli ifade, kapalı ifadenin kullanılabildiği bağlamlarla tamamen aynı bağlamlarda ve aksi belirtilmediği sürece aynı anlamla kullanılabilir .

Soru : Hangi bağlamlarda ekstra parantezler, temel operatör önceliğini geçersiz kılmak dışında bir C ++ programının anlamını değiştirir?

NOT : İşaretçi-üye sözdiziminin &qualified-idparantezsiz olarak kısıtlanmasının kapsam dışında olduğunu düşünüyorum çünkü farklı anlamlara sahip iki sözdizimine izin vermek yerine sözdizimini kısıtlıyor . Benzer şekilde, önişlemci makro tanımlarında parantez kullanımı da istenmeyen operatör önceliğine karşı koruma sağlar.


"Üyeye işaretçi & (nitelenmiş kimlik) çözümlemesinin operatör önceliğine sahip bir uygulama olduğunu düşünüyorum." -- Neden? Parantezleri atlarsanız, &(C::f)işleneni &hala olur C::f, değil mi?

@hvd expr.unary.op/4: Üyeye bir işaretçi, yalnızca bir açık &kullanıldığında ve işleneni parantez içine alınmayan nitelikli bir kimlik olduğunda oluşturulur .
TemplateRex

Peki, bunun operatör önceliğiyle ne ilgisi var? (Boşverin, düzenlediğiniz soru bunu

@hvd güncellendi, bu Soru-Cevap bölümünde RHS'yi LHS ile karıştırıyordum ve burada parensler (), işaretçi-üye seçiciye göre işlev çağrısının önceliğini geçersiz kılmak için kullanılıyor::*
TemplateRex

1
Bence hangi vakaların dikkate alınması gerektiği konusunda biraz daha kesin olmalısın. Örneğin, bir tür adının etrafındaki parantezler, onu C stili bir atama operatörü haline getirmek için (bağlam ne olursa olsun) parantezli bir ifade oluşturmaz. Öte yandan teknik olarak if veya while'dan sonraki koşul parantezli bir ifade olduğunu söyleyebilirim , ancak parantezler burada sözdiziminin bir parçası olduğu için dikkate alınmamalıdır. Operatör önceliği dahil olsun veya olmasın, parantezler olmadan ifade artık tek bir birim olarak çözümlenmeyeceği durumda IMO da olmamalıdır.
Marc van Leeuwen

Yanıtlar:


114

TL; DR

Ekstra parantezler, aşağıdaki bağlamlarda bir C ++ programının anlamını değiştirir:

  • bağımsız değişkene bağlı ad aramasını önleme
  • liste bağlamlarında virgül operatörünü etkinleştirme
  • sinir bozucu çözümlemelerin belirsizlik çözümü
  • decltypeİfadelerde referanslığın çıkarılması
  • önişlemci makro hatalarını önleme

Bağımsız değişkene bağlı ad aramasını önleme

Standart Ek A'da ayrıntılı olarak açıklandığı gibi, post-fix expressionformun (expression)a biçimi a'dır primary expression, ancak bir değildir id-expressionve bu nedenle bir değildir unqualified-id. Bu (fun)(arg), geleneksel forma kıyasla formun işlev çağrılarında bağımsız değişkene bağlı ad aramasının önlendiği anlamına gelir fun(arg).

3.4.2 Bağımsız değişkene bağlı ad araması [basic.lookup.argdep]

1 Bir işlev çağrısındaki (5.2.2) sonek ifadesi niteliksiz bir kimlik olduğunda , olağan niteliksiz arama (3.4.1) sırasında dikkate alınmayan diğer ad alanları aranabilir ve bu ad alanlarında, ad alanı-kapsam arkadaş işlevi veya Aksi halde görünmeyen işlev şablonu bildirimleri (11.3) bulunabilir. Aramadaki bu değişiklikler, bağımsız değişkenlerin türlerine (ve şablon şablon bağımsız değişkenleri için, şablon bağımsız değişkeninin ad alanına) bağlıdır. [ Misal:

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
}

—Son örnek]

Virgül operatörünü liste bağlamlarında etkinleştirme

Virgül operatörü, çoğu liste benzeri bağlamda (işlev ve şablon bağımsız değişkenleri, başlatıcı listeleri vb.) Özel bir anlama sahiptir. Bu a, (b, c), dtür bağlamlarda formun parantezleri , normal forma kıyasla virgül operatörünü etkinleştirebilira, b, c, d parantezleri, virgül operatörünün uygulanmadığı .

5.18 Virgül operatörü [expr.comma]

2 Virgülün özel bir anlamın verildiği bağlamlarda, [Örnek: işlevler için bağımsız değişkenler listelerinde (5.2.2) ve başlatıcı listelerinde (8.5) - son örnek] Madde 5'te açıklandığı gibi virgül operatörü yalnızca parantez içinde görünebilir. [ Misal:

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

ikincisi 5 değerine sahip üç bağımsız değişkeni vardır - son örnek]

Sinir bozucu ayrıştırmaların belirsizlik çözünürlüğü

C ve onun gizli işlev bildirimi sözdizimi ile geriye dönük uyumluluk, can sıkıcı ayrıştırmalar olarak bilinen şaşırtıcı ayrıştırma belirsizliklerine yol açabilir. Esasen, bildirim olarak ayrıştırılabilen her şey tek bir bildirim olarak çözümlenecektir. rakip ayrıştırma da geçerli olsa bile .

6.8 Belirsizlik çözümü [stmt.ambig]

1 İfade ifadeleri ve bildirimleri içeren dilbilgisinde bir belirsizlik vardır : En soldaki alt ifadesi, işlev tarzı açık tür dönüşümü (5.2.3) olan bir ifade-ifadesi, ilk açıklamanın bir ( . bu durumlarda ifadesi bir beyanıdır .

8.2 Belirsizlik çözümü [dcl.ambig.res]

1 6.8'de belirtilen bir fonksiyon tarzı atama ile bir bildirim arasındaki benzerlikten kaynaklanan belirsizlik, bir bildirim bağlamında da ortaya çıkabilir . Bu bağlamda, seçim, bir parametre adı etrafında yedekli bir parantez kümesi içeren bir işlev bildirimi ile başlatıcı olarak bir işlev tarzı döküm içeren bir nesne bildirimi arasındadır. 6.8'de belirtilen belirsizlikler için olduğu gibi, karar, muhtemelen bir beyan olabilecek herhangi bir yapıyı bir beyan olarak değerlendirmektir . [Not: Bir bildirimin belirsizliği, işlev olmayan bir tür atama ile, başlatmayı belirtmek için an = ile veya parametre adının etrafındaki gereksiz parantezler kaldırılarak açıkça kaldırılabilir. —End note] [Örnek:

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
}

—Son örnek]

Bunun ünlü bir örneği, Etkili STL kitabının 6. maddesinde Scott Meyers tarafından popüler hale getirilen En Vexing Ayrıştırmadır :

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

Bu data, dönüş türü olan bir işlevi bildirir list<int>. İşlev verileri iki parametre alır:

  • İlk parametre adlandırılır dataFile. Tipidir istream_iterator<int>. Etrafındaki parantezler dataFilegereksizdir ve dikkate alınmaz.
  • İkinci parametrenin adı yoktur. Türü, hiçbir şey almayan ve bir istream_iterator<int>.

İlk fonksiyon argümanının etrafına fazladan parantez koymak (ikinci argümanın etrafına parantezler geçersizdir) belirsizliği çözecektir

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

C ++ 11, birçok bağlamda bu tür çözümleme sorunlarını yan adımlara atmaya izin veren küme ayracı başlatıcı sözdizimine sahiptir.

Referanslık çıkarılması decltype ifadeler

autoTür kesintisinin aksine decltype, referanslığın (ldeğer ve rdeğer referansları) çıkarılmasına izin verir. Kurallar decltype(e)ve decltype((e))ifadeleri birbirinden ayırır :

7.1.6.2 Basit tür belirticiler [dcl.type.simple]

4 Bir ifade için e, ile gösterilen türdecltype(e) aşağıdaki gibi tanımlanır:

- eparantezsiz bir id ifadesi veya parantezsiz bir sınıf üyesi erişimiyse (5.2.5), decltype(e)tarafından adlandırılan varlığın türüdür e. Böyle bir varlık yoksa veya ebir dizi aşırı yüklenmiş işlevi adlandırırsa, program bozuktur;

- aksi takdirde, eğer ebir xvalue ise, decltype(e)is T&&, nerede Ttürü e;

- aksi takdirde, ebir lvalue olduğu, decltype(e)olduğu T&, burada Ttürüdüre ;

- aksi takdirde, decltype(e)türüdür e.

Decltype tanımlayıcısının işleneni, değerlendirilmemiş bir işlenendir (Madde 5). [ Misal:

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&

—Son örneği] [Not: decltype(auto)İçerdiği türleri belirleme kuralları 7.1.6.4'te belirtilmiştir. - notu gönder]

Kuralları decltype(auto), başlatan ifadenin RHS'sindeki ekstra parantezler için benzer bir anlama sahiptir. İşte C ++ SSS'den bir örnek ve bu ilgili Soru-Cevap

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

İlk sonuç, yerel değişkene referans olan stringikinci döner .string &str

Önişlemci makroyla ilgili hataları önleme

En yaygın olanları aşağıda listelenen uygun C ++ diliyle etkileşimlerinde önişlemci makroları olan bir dizi incelik vardır.

  • #define TIMES(A, B) (A) * (B);İstenmeyen operatör önceliğinden kaçınmak için makro tanımının içindeki makro parametreleri etrafında parantezler kullanmak (örneğin, TIMES(1 + 2, 2 + 1)burada 9 verir, ancak parantezler (A)ve(B)
  • İçinde virgül bulunan makro bağımsız değişkenlerin etrafına parantez kullanarak assert((std::is_same<int, int>::value)); aksi takdirde derlenmez
  • Dahil edilen başlıklarda makro genişletmeye karşı koruma sağlamak için bir işlevin etrafında parantezler kullanma: (min)(a, b)(ADL'yi devre dışı bırakmanın istenmeyen yan etkisiyle)

7
Gerçekten programı anlamını değiştirmek, ancak en iyi uygulama ve derleyici tarafından yayılan uyarıları etkiler yok: Ekstra parantez kullanılmalıdır if/ whileifadesi bir atama olup olmadığını. Örneğin if (a = b)- uyarı (demek istediniz ==mi?), Süre if ((a = b))- uyarı yok.
Csq

@Csq teşekkürler, iyi bir gözlem, ancak bu belirli bir derleyici tarafından bir uyarıdır ve Standart tarafından zorunlu tutulmaz. Bunun, bu Soru-Cevap bölümünün dil avukatı niteliğine uyduğunu sanmıyorum.
TemplateRex

Does (min)(a, b)(kötülük MACRO min(A, B)) bağımsız ve bağımlı ad arama önleme parçası mı?
Jarod42

@ Jarod42 Sanırım öyle, ama bunun gibi ve diğer kötü makroları sorunun kapsamı dışında düşünelim :-)
TemplateRex

5
@JamesKanze: OP ve TemplateRex'in aynı kişi olduğunu unutmayın ^ _ ^
Jarod42

4

Genel olarak, programlama dilleri de, "ekstra" parantez olduklarını ima değil sözdizimsel ayrıştırma sırasını değiştirerek veya anlam. Kodu okuyan insanların yararı için sırayı netleştirmek (operatör önceliği) için ekleniyorlar ve tek etkileri derleme sürecini biraz yavaşlatmak ve kodu anlamada insan hatalarını azaltmak (muhtemelen genel geliştirme sürecini hızlandırmak) olacaktır. ).

Bir parantez kümesi bir ifadenin ayrıştırılma şeklini gerçekten değiştirirse , tanım gereği ekstra değildirler . Geçersiz / geçersiz bir ayrıştırmayı yasal olana dönüştüren parantezler "ekstra" değildir, ancak , kötü bir dil tasarımına işaret edebilir .


2
tam olarak ve bu, aksi belirtilmediği sürece C ++ 'da da genel kuraldır (sorudaki Standart alıntıya bakın) . Bu "zayıflıklara" işaret etmek, bu Soru ve Cevapların amacı idi.
TemplateRex
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.