Moja próba zainicjowania wartości jest interpretowana jako deklaracja funkcji, a dlaczego nie A (()); rozwiązać?

158

Wśród wielu rzeczy, których nauczył mnie Stack Overflow, jest to, co jest znane jako „najbardziej irytująca analiza”, co jest klasycznie przedstawiane za pomocą wiersza takiego jak

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

Chociaż dla większości wydaje się to intuicyjnie, że jest to deklaracja obiektu atypu A, przyjmująca Bobiekt tymczasowy jako parametr konstruktora, w rzeczywistości jest to deklaracja funkcji azwracającej an A, pobierającej wskaźnik do funkcji, która zwraca Bi sama nie przyjmuje żadnych parametrów . Podobnie linia

A a(); //declares a function

również należy do tej samej kategorii, ponieważ zamiast obiektu deklaruje funkcję. Teraz, w pierwszym przypadku, typowym obejściem tego problemu jest dodanie dodatkowego zestawu nawiasów / nawiasów wokół znaku B(), ponieważ kompilator zinterpretuje to jako deklarację obiektu

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

Jednak w drugim przypadku zrobienie tego samego prowadzi do błędu kompilacji

A a(()); //compile error

Moje pytanie brzmi: dlaczego? Tak, bardzo dobrze zdaję sobie sprawę, że poprawnym rozwiązaniem jest zmiana tego na A a;, ale jestem ciekawy, co to jest dodatkowe działanie ()dla kompilatora w pierwszym przykładzie, który następnie nie działa po ponownym zastosowaniu go w drugi przykład. Czy A a((B()));obejście zawiera określony wyjątek zapisany w standardzie?

GRB
źródło
20
(B())to tylko wyrażenie w C ++, nic więcej. To nie jest żaden wyjątek. Jedyną różnicą, jaką to robi, jest to, że nie ma możliwości, aby można go było przeanalizować jako typ, więc tak nie jest.
Pavel Minaev,
12
Należy również zauważyć, że drugi przypadek, A a();to nie z tej samej kategorii. W przypadku kompilatora nigdy nie ma innego sposobu, aby go przeanalizować: inicjalizator w tym miejscu nigdy nie składa się z pustych nawiasów, więc zawsze jest to deklaracja funkcji.
Johannes Schaub - litb
11
Doskonały punkt litb jest subtelny, ale ważny i wart podkreślenia - powodem, dla którego niejednoznaczność istnieje w tej deklaracji `` A a (B ()) '' jest analiza `` B () '' -> może to być zarówno wyrażenie, jak i deklaracja, a kompilator musi „wybrać” decl zamiast wyrażenia - więc jeśli B () jest decl, to „a” może być tylko funkcją func decl (a nie zmienną decl). Gdyby '()' mogło być inicjalizatorem 'A a ()' byłoby niejednoznaczne - ale nie expr vs decl, ale var decl vs func decl - nie ma reguły preferującej jedną deklację nad drugą - i tak '() 'jest po prostu niedozwolone jako inicjator tutaj - i nie ma wątpliwości.
Faisal Vali
6
A a();to nie stanowi przykład najbardziej irytującej parse . To po prostu deklaracja funkcji, tak jak w C.
Pete Becker,
2
„Prawidłowe„ obejście ”to zmiana na A a;„ ”jest błędne. To nie da ci inicjalizacji typu POD. Aby uzyskać inicjalizację, napisz A a{};.
Pozdrawiam i hth. - Alf

Odpowiedzi:

70

Nie ma oświeconej odpowiedzi, to tylko dlatego, że język C ++ nie zdefiniował jej jako poprawnej składni ... Tak jest z definicji języka.

Jeśli masz wyrażenie wewnątrz, to jest ono ważne. Na przykład:

 ((0));//compiles

Jeszcze prościej: ponieważ (x)jest prawidłowym wyrażeniem w C ++, a ()nie jest.

Aby dowiedzieć się więcej o tym, jak definiuje się języki i jak działają kompilatory, powinieneś zapoznać się z formalną teorią języka, a dokładniej gramatyką bezkontekstową (CFG) i powiązanymi materiałami, takimi jak maszyny skończone. Jeśli jesteś tym zainteresowany, chociaż strony wikipedii nie wystarczą, musisz zdobyć książkę.

Brian R. Bondy
źródło
45
Jeszcze prościej: ponieważ (x)jest prawidłowym wyrażeniem w C ++, a ()nie jest.
Pavel Minaev,
Przyjąłem tę odpowiedź, dodatkowo komentarz Pawła do mojego początkowego pytania bardzo mi pomógł
GRB
29

Deklaratory funkcji C.

Przede wszystkim jest C. W C A a()jest deklaracja funkcji. Na przykład putcharma następującą deklarację. Zwykle takie deklaracje są przechowywane w plikach nagłówkowych, jednak nic nie stoi na przeszkodzie, aby napisać je ręcznie, jeśli wiesz, jak wygląda deklaracja funkcji. Nazwy argumentów są opcjonalne w deklaracjach, więc pominąłem je w tym przykładzie.

int putchar(int);

Pozwala to na pisanie kodu w ten sposób.

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

C pozwala również na zdefiniowanie funkcji, które przyjmują funkcje jako argumenty, z ładną, czytelną składnią, która wygląda jak wywołanie funkcji (cóż, jest czytelna, o ile nie zwrócisz wskaźnika do funkcji).

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

Jak wspomniałem, C pozwala na pomijanie nazw argumentów w plikach nagłówkowych, dlatego output_resultw pliku nagłówkowym wyglądałoby to tak.

int output_result(int());

Jeden argument w konstruktorze

Nie rozpoznajesz tego? Cóż, pozwól, że ci przypomnę.

A a(B());

Tak, to dokładnie ta sama deklaracja funkcji. Ajest int, ajest output_resulti Bjest int.

Możesz łatwo zauważyć konflikt C z nowymi funkcjami C ++. Mówiąc dokładniej, konstruktory są nazwą klasy i nawiasami, a alternatywna składnia deklaracji z ()zamiast =. Z założenia C ++ stara się być kompatybilny z kodem C, dlatego musi sobie z tym poradzić - nawet jeśli praktycznie nikogo to nie obchodzi. Dlatego stare funkcje C mają pierwszeństwo przed nowymi funkcjami C ++. Gramatyka deklaracji próbuje dopasować nazwę jako funkcję, przed powrotem do nowej składni, ()jeśli to się nie powiedzie.

Gdyby jedna z tych funkcji nie istniała lub miała inną składnię (jak {}w C ++ 11), ten problem nigdy nie wystąpiłby w przypadku składni z jednym argumentem.

Teraz możesz zapytać, dlaczego A a((B()))działa. Cóż, zadeklarujmy output_resultz bezużytecznymi nawiasami.

int output_result((int()));

To nie zadziała. Gramatyka wymaga, aby zmienna nie była umieszczona w nawiasach.

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

Jednak C ++ oczekuje tutaj standardowego wyrażenia. W C ++ możesz napisać następujący kod.

int value = int();

I następujący kod.

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

C ++ oczekuje, że wyrażenie wewnątrz nawiasów będzie ... cóż ... wyrażeniem, w przeciwieństwie do tego, jakiego oczekuje typ C. Nawiasy tutaj nic nie znaczą. Jednak przez wstawienie bezużytecznych nawiasów deklaracja funkcji C nie jest dopasowywana, a nowa składnia może być dopasowana poprawnie (co po prostu oczekuje wyrażenia, takiego jak 2 + 2).

Więcej argumentów w konstruktorze

Z pewnością jeden argument jest fajny, ale co z dwoma? Nie chodzi o to, że konstruktorzy mogą mieć tylko jeden argument. Jedną z wbudowanych klas, która przyjmuje dwa argumenty, jeststd::string

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

To wszystko jest w porządku (technicznie rzecz biorąc, najbardziej irytująca byłaby analiza, gdyby była zapisana jako std::string wat(int(), char()), ale bądźmy szczerzy - kto by to napisał? Ale załóżmy, że ten kod ma irytujący problem. Można by założyć, że musisz wstawić wszystko w nawiasach.

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

Nie całkiem.

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

Nie jestem pewien, dlaczego g ++ próbuje przekonwertować charna const char *. W każdym razie konstruktor został wywołany tylko z jedną wartością typu char. Nie ma przeciążenia, które ma jeden argument typu char, dlatego kompilator jest zdezorientowany. Możesz zapytać - dlaczego argument jest typu char?

(100, '.')

Tak, ,tutaj jest operator przecinka. Operator przecinka przyjmuje dwa argumenty i podaje argument po prawej stronie. Nie jest to naprawdę przydatne, ale należy je poznać z mojego wyjaśnienia.

Zamiast tego, aby rozwiązać najbardziej irytującą analizę, potrzebny jest następujący kod.

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

Argumenty znajdują się w nawiasach, a nie w całym wyrażeniu. W rzeczywistości tylko jedno z wyrażeń musi znajdować się w nawiasach, ponieważ wystarczy nieznacznie oderwać się od gramatyki języka C, aby użyć funkcji C ++. Rzeczy doprowadzają nas do punktu zerowego argumentów.

Zero argumentów w konstruktorze

Być może zauważyłeś eighty_fourfunkcję w moim wyjaśnieniu.

int eighty_four();

Tak, dotyczy to również najbardziej irytującej analizy. To poprawna definicja, którą najprawdopodobniej widzieliście, jeśli utworzyliście pliki nagłówkowe (a powinniście). Dodanie nawiasów nie rozwiązuje tego problemu.

int eighty_four(());

Dlaczego to jest takie? Cóż, ()to nie jest wyrażenie. W C ++ musisz umieścić wyrażenie w nawiasach. Nie możesz pisać auto value = ()w C ++, ponieważ ()nic nie znaczy (a nawet gdyby tak było, jak pusta krotka (zobacz Python), byłby to jeden argument, a nie zero). Praktycznie oznacza to, że nie możesz używać skróconej składni bez użycia składni C ++ 11 {}, ponieważ nie ma wyrażeń, które można by umieścić w nawiasach, a gramatyka języka C dla deklaracji funkcji będzie zawsze miała zastosowanie.

Konrad Borowski
źródło
12

Mógłbyś zamiast tego

A a(());

posługiwać się

A a=A();
user265149
źródło
32
„Lepsze obejście” nie jest równoważne. int a = int();inicjuje aod 0, int a;pozostawia aniezainicjowane. Prawidłowym obejściem jest użycie A a = {};dla agregacji, A a;gdy domyślna inicjalizacja robi to, co chcesz, i A a = A();we wszystkich innych przypadkach - lub po prostu używaj A a = A();konsekwentnie. W C ++ 11 po prostu użyjA a {};
Richard Smith,
6

Najbardziej wewnętrzne pareny w twoim przykładzie byłyby wyrażeniem, aw C ++ gramatyka definiuje an expressionjako jeden assignment-expressionlub inny, expressionpo którym następuje przecinek i inny assignment-expression(Dodatek A.4 - Podsumowanie gramatyki / Wyrażenia).

Gramatyka dalej definiuje assignment-expressionwyrażenie jako jeden z kilku innych typów wyrażeń, z których żaden nie może być niczym (lub tylko białą spacją).

Więc powodem, dla którego nie możesz mieć, A a(())jest po prostu to, że gramatyka na to nie pozwala. Nie mogę jednak odpowiedzieć, dlaczego ludzie, którzy stworzyli C ++, nie pozwolili na to szczególne użycie pustych parenów jako specjalnego przypadku - przypuszczam, że woleliby nie umieszczać tak specjalnego przypadku, gdyby istniał rozsądna alternatywa.

Michael Burr
źródło