Değer başlatma girişimim bir işlev bildirimi olarak yorumlanıyor ve neden A a (()); çöz onu?


158

Stack Overflow'un bana öğrettiği birçok şey arasında, "en sinir bozucu ayrıştırma" olarak bilinen şey, klasik gibi bir çizgi ile gösterilmiştir

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

Bu, çoğu için, sezgisel olarak, bir yapıcı parametresi olarak geçici bir nesneyi alarak, bir atür nesnenin bildirimi gibi görünse de, aslında bir işlevi döndüren , geri dönen bir işleve bir işaretçi alarak ve hiçbir parametre almayan bir işlevin beyanıdır. . Benzer şekilde çizgiABaAB

A a(); //declares a function

aynı kategoriye girer, çünkü bir nesne yerine bir işlev bildirir. Şimdi, ilk durumda, bu sorunun olağan geçici çözümü B(), derleyicinin daha sonra bir nesnenin bildirimi olarak yorumlayacağı için , ek bir parantez / parantez kümesi eklemektir.

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

Ancak, ikinci durumda, aynı işlemi yapmak bir derleme hatasına yol açar

A a(()); //compile error

Sorum şu, neden? Evet, doğru 'geçici çözümün' bunu değiştirmek olduğunu çok iyi A a;biliyorum, ancak ()ilk örnekte derleyicinin ekstra olanın , yeniden uygularken çalışmadığı şeyin ne olduğunu merak ediyorum . ikinci örnek. Mı A a((B()));geçici çözüm standart yazılır belirli istisna mı?


20
(B())sadece bir C ++ ifadesi, başka bir şey değil. Herhangi bir istisna değildir. Yaptığı tek fark, büyük olasılıkla bir tür olarak ayrıştırılamamasının hiçbir yolu olmamasıdır ve bu yüzden değildir.
Pavel Minaev

12
Ayrıca ikinci vaka, unutulmamalıdır A a();olduğu değil aynı kategorinin. İçin derleyici , bunu ayrıştırmak için herhangi farklı bir yolu var asla: bu her zaman bir işlev beyanıdır böylece yerde bir başlatıcı asla boş parantez oluşur.
Johannes Schaub - litb

11
litb'nin mükemmel noktası, ince ama önemli bir konudur ve vurgulamaya değer - bu 'A a (B ())' bildirgesindeki belirsizliğin nedeni 'B ()' ayrıştırılmasındadır -> hem bir ifade hem de bir bildirim ve derleyici ifade üzerinde 'seçim' yapmalıdır - eğer B () bir etiketse, 'a' sadece bir işlev etiketi olabilir (değişken bir etiket değil). '()' Başlatıcı olmasına izin verildiyse 'A a ()' belirsiz olurdu - ancak decl'e karşı ekspr değil, var decl vs func decl - bir decl'i diğerine tercih etme kuralı yoktur - ve böylece '() “burada bir başlatıcı olarak izin verilmiyor - ve belirsizlik artmıyor.
Faisal Vali

6
A a();olduğu değil bir örnektir en can sıkıcı parse . Bu sadece C'deki gibi bir işlev beyanıdır
Pete Becker

2
"doğru" geçici çözüm "olarak değiştirmek A a;" yanlıştır. Bu size bir POD türünün başlatılmasını sağlamaz. Başlatma almak için yazın A a{};.
Şerefe ve s. - Alf

Yanıtlar:


70

Aydınlanmış bir cevap yoktur, bunun nedeni sadece C ++ dili tarafından geçerli sözdizimi olarak tanımlanmamış olmasıdır ... Yani bu, dilin tanımı gereğidir.

İçinde bir ifadeniz varsa, o zaman geçerlidir. Örneğin:

 ((0));//compiles

Daha basit (x)ifade : çünkü geçerli bir C ++ ifadesi ()değil.

Dillerin nasıl tanımlandığı ve derleyicilerin nasıl çalıştığı hakkında daha fazla bilgi edinmek için Biçimsel dil teorisi veya daha spesifik olarak Bağlamdan Bağımsız Gramerler (CFG) ve sonlu durum makineleri gibi ilgili materyalleri öğrenmelisiniz . Eğer wikipedia sayfaları yeterli olmayacak olsa da, bir kitap almanız gerekecek.


45
Daha basit (x)ifade : çünkü geçerli bir C ++ ifadesi ()değil.
Pavel Minaev

Bu yanıtı kabul ettim, ayrıca Pavel'in ilk soruma yaptığı yorum bana çok yardımcı oldu
GRB


29

C işlev bildiricileri

Her şeyden önce, C vardır. C'de A a()fonksiyon beyanıdır. Örneğin putchar, aşağıdaki bildirime sahiptir. Normalde, bu tür bildirimler başlık dosyalarında saklanır, ancak işlev bildiriminin nasıl göründüğünü biliyorsanız, hiçbir şey bunları elle yazmanızı engellemez. Bağımsız değişken adları bildirimlerde isteğe bağlıdır, bu nedenle bu örnekte atladım.

int putchar(int);

Bu, kodu böyle yazmanıza olanak tanır.

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

C ayrıca işlevleri işlev çağrısı gibi görünen güzel okunabilir sözdizimiyle işlevleri bağımsız değişken olarak tanımlayan işlevleri tanımlamanıza olanak tanır (bir işaretçiyi işlevine döndürmeyeceğiniz sürece okunabilir).

#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);
}

Bahsettiğim gibi C, başlık dosyalarında argüman adlarının çıkarılmasına izin verir, bu nedenle output_resultbaşlık dosyasında şöyle görünür.

int output_result(int());

Yapıcıda bir argüman

Bunu tanımıyor musun? Size hatırlatmama izin verin.

A a(B());

Evet, tam olarak aynı işlev beyanı. Aolduğunu int, aolduğunu output_resultve Bolduğunu int.

C ++ 'nın yeni özellikleriyle C çatışmasını kolayca fark edebilirsiniz. Tam olarak söylemek gerekirse, yapıcılar sınıf adı ve parantez olup, ()yerine alternatif bildirim sözdizimidir =. Tasarım gereği, C ++ C koduyla uyumlu olmaya çalışır ve bu nedenle bu durumla uğraşmak zorundadır - pratikte kimse umurunda olmasa bile. Bu nedenle, eski C özelliklerinin yeni C ++ özelliklerine göre önceliği vardır. Bildirimlerin dilbilgisi, ()başarısız olursa yeni sözdizimine dönmeden önce adı işlev olarak eşleştirmeye çalışır .

Bu özelliklerden biri mevcut değilse veya farklı bir sözdizimine sahip olsaydı ( {}C ++ 11'deki gibi), bu sorun hiçbir zaman tek bir argümanla sözdizimi için olmazdı.

Şimdi neden A a((B()))işe yaradığını sorabilirsiniz . output_resultYararsız parantezler ile açıklayalım .

int output_result((int()));

Çalışmaz. Dilbilgisi, değişkenin parantez içinde olmamasını gerektirir.

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

Ancak, C ++ burada standart ifade bekliyor. C ++ ile, aşağıdaki kodu yazabilirsiniz.

int value = int();

Ve aşağıdaki kod.

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

C ++, parantez içindeki ifadenin, C türünün aksine, ... iyi ... ifadesi olmasını bekler. Parantez burada bir şey ifade etmiyor. Bununla birlikte, işe yaramaz parantezler eklenerek, C işlevi bildirimi eşleştirilmez ve yeni sözdizimi düzgün şekilde eşleştirilebilir (bu, yalnızca bir ifade bekler 2 + 2).

Yapıcıdaki diğer argümanlar

Elbette bir argüman güzel, ama ikisine ne olacak? Yapıcıların sadece bir argümanı olabilir. İki argüman alan yerleşik sınıflardan biristd::string

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

Bu iyi ve güzel (teknik olarak, eğer yazılırsa en sinir bozucu ayrıştırma olurdu std::string wat(int(), char()), ama dürüst olalım - bunu kim yazacak? Ama diyelim ki bu kodun can sıkıcı bir sorunu var. parantez içindeki her şey.

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

Pek değil.

<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>::
     ^

G ++ çalışır dönüştürmek için neden emin değilim chariçin const char *. Her iki durumda da, yapıcı yalnızca bir tür değerle çağrıldı char. Bir tür argümanı olan aşırı yük yoktur char, bu nedenle derleyici karıştırılır. Siz sorabilirsiniz - neden argüman char türünde?

(100, '.')

Evet, ,işte virgül operatörü. Virgül operatörü iki argüman alır ve sağ taraf argümanını verir. Gerçekten kullanışlı değil, ama açıklamam için bilinmesi gereken bir şey.

Bunun yerine, en sinir bozucu ayrışmayı çözmek için aşağıdaki kod gereklidir.

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

Argümanlar parantez içinde, ifadenin tamamı değil. Aslında, C ++ özelliğini kullanmak için C dilbilgisinden biraz kopmak yeterli olduğundan, ifadelerden sadece birinin parantez içinde olması gerekir. İşler bizi sıfır argüman noktasına getiriyor.

Yapıcıdaki sıfır argümanlar

eighty_fourAçıklamamdaki işlevi fark etmiş olabilirsiniz .

int eighty_four();

Evet, bu aynı zamanda en sinir bozucu ayrışmadan da etkilenir. Bu geçerli bir tanımdır ve başlık dosyaları oluşturduysanız büyük olasılıkla görmüşsünüzdür (ve yapmanız gerekir). Parantez eklemek sorunu çözmez.

int eighty_four(());

Neden böyle? Şey, ()bir ifade değildir. C ++ ile, parantezler arasına bir ifade koymanız gerekir. auto value = ()C ++ ile yazamazsınız , çünkü ()hiçbir şey ifade etmez (ve boş demet gibi (Python'a bakın), sıfır değil tek bir argüman olurdu). Pratik olarak bu, C ++ 11 sözdizimini kullanmadan steno sözdizimini kullanamayacağınız anlamına gelir {}, çünkü parantez içine alınacak ifade yoktur ve işlev bildirimleri için C dilbilgisi her zaman uygulanır.


12

Bunun yerine

A a(());

kullanım

A a=A();

32
'Daha iyi geçici çözüm' eşdeğer değildir. int a = int();başlatılırken a0 ile, int a;yapraklar abaşlatılmamış. Doğru çözüm kullanmaktır A a = {};agrega için A a;varsayılan-başlatma istediğini yapar ve A a = A();tüm diğer durumlarda - ya da sadece kullanmak A a = A();tutarlı. C ++ 11'de, sadece kullanınA a {};
Richard Smith

6

Örneğinizin en içteki parensleri bir ifade olacaktır ve C ++ 'da dilbilgisi expressionbir assignment-expressionya da diğeri ve expressionardından virgül ve diğeri olarak tanımlamaktadır assignment-expression(Ek A.4 - Dilbilgisi özeti / İfadeler).

Dilbilgisi ayrıca a'yı assignment-expression, hiçbiri hiçbir şey (veya yalnızca boşluk) olamayacak diğer birkaç ifade türünden biri olarak tanımlar .

Bu yüzden sahip olmamanızın nedeni A a(())sadece dilbilgisinin buna izin vermemesi. Ancak, neden C ++ yaratan insanların bir çeşit özel durum olarak boş parenslerin bu özel kullanımına izin vermediğini cevaplayamıyorum - sanırım eğer böyle özel bir davaya girmemeyi tercih ediyorlardı makul bir alternatif.

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.