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?


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ę.


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.


12

Mógłbyś zamiast tego

A a(());

posługiwać się

A a=A();

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.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.