Dlaczego muszę wyraźnie wpisać słowo kluczowe „auto”?

80

Przechodzę w kierunku C ++ 11 z C ++ 98 i zapoznałem się ze autosłowem kluczowym. Zastanawiałem się, dlaczego musimy jawnie zadeklarować, autoczy kompilator może automatycznie wydedukować typ. Wiem, że C ++ jest językiem silnie typizowanym i jest to reguła, ale czy nie można było osiągnąć tego samego wyniku bez jawnego zadeklarowania zmiennej auto?

Farsan Rashid
źródło
Pamiętaj, że w rodzinie C rozróżniana jest wielkość liter. Przejdź do debugowania kodu JS, w którym autor pominął „var” i użyj oddzielnej zmiennej o nazwach takich jak „Bob”, „bob” i „boB”. Fuj.
PTwr,
46
Nawet gdyby to było możliwe, byłby to umiarkowanie okropny pomysł. Prawdopodobnie największą słabością Pythona (i podobnych języków) jest brak składni deklaracji. Głównym źródłem błędów jest to, że proste przypisanie spowoduje utworzenie nowej zmiennej.
Konrad Rudolph
@KonradRudolph: JS nie jest jednak lepszy ze swoją składnią deklaracji. Myślę, że chcieli powiedzieć, że jest to niezdolność do precyzyjnego ograniczenia zakresu zmiennej.
user541686
4
@Mehrdad „zmienia semantykę” ≠ „obowiązkowe”. Problemem jest to, że JavaScript nie akceptuje ukryte deklaracji. Tak, są semantycznie różne, ale to w najmniejszym stopniu nie pomaga.
Konrad Rudolph
1
Zobacz także podwójne pytanie „dlaczego prawdziwi użytkownicy Perla używają słowa kluczowego„ moje ” stackoverflow.com/questions/8023959/why-use-strict-and-warnings/ ...
Yann TM

Odpowiedzi:

156

Porzucenie treści jawnych autozłamałoby język:

na przykład

int main()
{
    int n;
    {
        auto n = 0; // this shadows the outer n.
    }
}

gdzie widać, że upuszczenie autonie zasłoni tego, co zewnętrzne n.

Batszeba
źródło
8
Pisałem dokładnie to samo. Odróżnienie przypisania od inicjalizacji wymagałoby arbitralnego wyboru ze strony normy. Ponieważ mamy już zasadę, że „wszystko, co mogłoby być deklaracją, jest deklaracją”, wchodzimy w bardzo mętną wodę.
StoryTeller - Unslander Monica
4
To nie jest problem. Podobnie jak golang, możesz oczywiście użyć czegoś takiego, jak n := 0wprowadzenie nowej zmiennej. Dlaczego autojest używane to pytanie oparte na opiniach.
llllllllll
23
@liliscent - Czy to jest oparte na opiniach? (a) Było to już zastrzeżone słowo kluczowe. (b) Znaczenie jest całkiem jasne. (c) Unika potrzeby wprowadzania nowych tokenów (np :=.). (d) To już pasuje do gramatyki. Myślę, że jest tu bardzo mało miejsca na opinię.
StoryTeller - Unslander Monica
2
@StoryTeller Nie potrzebujesz nowych tokenów, jeśli x = f()deklarujesz nową zmienną (jeśli jeszcze nie istnieje), uzyskując typ wartości zwracanej przez f`s ... Wymaganie auto do jawnego zadeklarowania zmiennej zmniejsza jednak ryzyko zadeklarowania nowych zmiennych przez przypadek (np. z powodu literówki ...).
Aconcagua
34
@Aconcagua - "deklaruje nową zmienną (jeśli jeszcze nie istnieje)" Ale cienie jest częścią języka i nadal musi działać, jak ilustruje to Batszeba. To większy problem, niż można sobie wyobrazić. Nie chodzi o projektowanie języka od zera, chodzi o zmianę żywego języka. O wiele trudniejsze do zrobienia. Coś jak zmiana koła w pędzącym samochodzie.
StoryTeller - Unslander Monica
40

Twoje pytanie pozwala na dwie interpretacje:

  • Dlaczego w ogóle potrzebujemy „auto”? Nie możemy go po prostu upuścić?
  • Dlaczego jesteśmy zobowiązani do używania auto? Czy nie możemy po prostu mieć tego w domyśle, jeśli nie jest dane?

Batszeba ładnie odpowiedziała na pierwszą interpretację, po drugie rozważ następujące (zakładając, że nie istnieją żadne inne deklaracje; hipotetycznie poprawny C ++):

int f();
double g();

n = f(); // declares a new variable, type is int;
d = g(); // another new variable, type is double

if(n == d)
{
    n = 7; // reassigns n
    auto d = 2.0; // new d, shadowing the outer one
}

To byłoby możliwe, inne języki uciec całkiem dobrze (no, oprócz kwestii shadowing być może) ... To nie jest tak w C ++, choć i kwestia (w sensie drugiej interpretacji) brzmi: dlaczego ?

Tym razem odpowiedź nie jest tak oczywista, jak w pierwszej interpretacji. Jedno jest jednak oczywiste: wyraźny wymóg słowa kluczowego sprawia, że ​​język jest bezpieczniejszy (nie wiem, czy to właśnie skłoniło komitet językowy do podjęcia decyzji, nadal pozostaje kwestią):

grummel = f();

// ...

if(true)
{
    brummel = f();
  //^ uh, oh, a typo...
}

Czy możemy się z tym zgodzić, nie potrzebując dalszych wyjaśnień?

Jeszcze większym niebezpieczeństwem związanym z niewymaganiem auto [jednak] jest to, że oznacza to, że dodanie zmiennej globalnej w miejscu odległym od funkcji (np. W pliku nagłówkowym) może zmienić to, co miało być deklaracją lokalnie- zmienną zakresową w tej funkcji na przypisanie do zmiennej globalnej ... z potencjalnie katastrofalnymi (iz pewnością bardzo mylącymi) konsekwencjami.

(cytowany komentarz psmears ze względu na jego znaczenie - dzięki za podpowiedź)

Aconcagua
źródło
24
Jeszcze większym niebezpieczeństwem związanym z niewymaganiem auto, moim zdaniem, jest to, że oznacza to, że dodanie zmiennej globalnej w miejscu oddalonym od funkcji (np. W pliku nagłówkowym) może zmienić to, co miało być deklaracją lokalnie zmienna w tej funkcji na przypisanie zmiennej globalnej ... z potencjalnie katastrofalnymi (iz pewnością bardzo zagmatwanymi) konsekwencjami.
psmears
1
@psmears Języki takie jak Python unikają tego, wymagając jawnej specyfikacji zmiennej jako globalnej / nielokalnej dla przypisań; domyślnie po prostu tworzy nową zmienną lokalną o tej nazwie. (Oczywiście można czytać ze zmiennej globalnej bez potrzeby stosowania global <variable>instrukcji). Oczywiście wymagałoby to jeszcze większej modyfikacji języka C ++, więc prawdopodobnie nie byłoby to wykonalne.
JAB
@JAB - tak, zdaję sobie z tego sprawę ... Nie wspomniałem o tym, ponieważ, jak mówisz, wymagałoby to jeszcze większej modyfikacji języka :)
psmears
FWIW, to, co napędzało komitet językowy, to najprawdopodobniej historia. AFAIK, kiedy C został pierwotnie napisany, zmienne lokalne były zapisywane na stosie, a C wymagał, aby wszystkie zmienne były jawnie zadeklarowane jako pierwsze w bloku. Pozwoliło to kompilatorowi określić wymagania dotyczące pamięci dla tego bloku przed skompilowaniem reszty kodu i wydać poprawną sekwencję instrukcji w celu przydzielenia miejsca na stosie. IIRC MOV R6 R5 SUB #nnn R6na PDP-11 przy założeniu, że R5 jest używany jako wskaźnik ramki, a R6 jest wskaźnikiem stosu. nnn to liczba potrzebnych bajtów pamięci.
dgnuff
2
Ludziom udaje się używać Pythona, który szczęśliwie deklaruje zmienną za każdym razem, gdy nowa nazwa pojawia się po lewej stronie przydziału (nawet jeśli jest to literówka). Ale uważam to za jedną z poważniejszych wad języka.
hobbs
15

czy nie można było osiągnąć tego samego wyniku bez wyraźnego zadeklarowania zmiennej auto?

Zamierzam nieco przeformułować Twoje pytanie w sposób, który pomoże Ci zrozumieć, dlaczego potrzebujesz auto:

Czy nie można było osiągnąć tego samego wyniku bez jawnego użycia symbolu zastępczego typu ?

Czy to nie było możliwe ? Oczywiście było to „możliwe”. Pytanie brzmi, czy warto byłoby to zrobić.

Większość składni w innych językach, które nie używają nazw typów, działa na jeden z dwóch sposobów. Istnieje sposób podobny do Go, w którym name := value;deklaruje zmienną. I jest sposób podobny do Pythona, w którym name = value;deklaruje nową zmienną, jeśli namenie została wcześniej zadeklarowana.

Załóżmy, że nie ma żadnych problemów z składniowe stosowania zarówno składni C ++ (choć już widzę, że identifiernastępuje :w C ++ oznacza „uczynić etykietę”). Więc co tracisz w porównaniu do symboli zastępczych?

Cóż, nie mogę już tego robić:

auto &name = get<0>(some_tuple);

Widzisz, autozawsze oznacza „wartość”. Jeśli chcesz uzyskać odniesienie, musisz jawnie użyć pliku &. I słusznie zakończy się niepowodzeniem, jeśli wyrażenie przypisania ma wartość prvalue. Żadna ze składni opartych na przypisaniu nie ma sposobu na rozróżnienie między odwołaniami a wartościami.

Teraz możesz sprawić, że takie składnie przypisania będą wywnioskować odwołania, jeśli dana wartość jest odwołaniem. Ale to oznaczałoby, że nie możesz:

auto name = get<0>(some_tuple);

To kopiuje z krotki, tworząc obiekt niezależny od some_tuple. Czasami właśnie tego chcesz. Jest to jeszcze bardziej przydatne, jeśli chcesz przejść z krotki za pomocą auto name = get<0>(std::move(some_tuple));.

OK, więc może moglibyśmy nieco rozszerzyć te składnie, aby uwzględnić to rozróżnienie. Może &name := value;lub &name = value;chciałoby wydedukować takie odniesienie auto&.

Ok dobrze. A co z tym:

decltype(auto) name = some_thing();

Och, zgadza się; C ++ ma w rzeczywistości dwa symbole zastępcze: autoidecltype(auto) . Podstawową ideą tej dedukcji jest to, że działa dokładnie tak, jakbyś to zrobił decltype(expr) name = expr;. Więc w naszym przypadku, jeśli some_thing()jest przedmiotem, wydedukuje obiekt. Jeśli some_thing()jest odniesieniem, wydedukuje odniesienie.

Jest to bardzo przydatne, gdy pracujesz w kodzie szablonu i nie masz pewności, jaka dokładnie będzie wartość zwracana funkcji. Świetnie nadaje się do spedycji i jest niezbędnym narzędziem, nawet jeśli nie jest szeroko stosowane.

Więc teraz musimy dodać więcej do naszej składni. name ::= value;oznacza „rób to, co decltype(auto)robi”. Nie mam odpowiednika dla wariantu Pythonic.

Patrząc na tę składnię, czy nie jest łatwo przypadkowo błędnie wpisać? Nie tylko to, prawie nie dokumentuje się samemu. Nawet jeśli nigdy wcześniej nie widziałeś decltype(auto), jest to wystarczająco duże i oczywiste, że możesz przynajmniej łatwo stwierdzić, że dzieje się coś wyjątkowego. Natomiast wizualna różnica między ::=i :=jest minimalna.

Ale to jest opinia; są bardziej istotne kwestie. Widzisz, wszystko to opiera się na używaniu składni przypisania. Cóż ... a co z miejscami, w których nie można używać składni przypisań? Lubię to:

for(auto &x : container)

Czy zmienimy to na for(&x := container)? Ponieważ wydaje się, że oznacza to coś zupełnie innego niż oparte na zakresie for. Wygląda na to, że jest to instrukcja inicjalizująca z regularnej forpętli, a nie oparta na zakresie for. Byłaby to również inna składnia niż przypadki niewyedukowane.

Również inicjalizacja kopiowania (używanie =) nie jest tym samym w C ++, co inicjalizacja bezpośrednia (przy użyciu składni konstruktora). Więc name := value;może nie działać w przypadkach, w których auto name(value)by to zrobił.

Jasne, możesz zadeklarować, że :=użyje bezpośredniej inicjalizacji, ale byłoby to całkiem zgodne ze sposobem, w jaki zachowuje się reszta C ++.

Jest jeszcze jedna rzecz: C ++ 14. Dało nam to jedną przydatną funkcję dedukcji: dedukcja typu zwrotu. Ale to jest oparte na symbolach zastępczych. Podobnie jak w przypadku zakresu, opiera forsię zasadniczo na nazwie typu, która jest wypełniana przez kompilator, a nie na jakiejś składni zastosowanej do określonej nazwy i wyrażenia.

Widzisz, wszystkie te problemy pochodzą z tego samego źródła: wymyślasz zupełnie nową składnię do deklarowania zmiennych. Deklaracje oparte na symbolach zastępczych nie musiały wymyślać nowej składni . Używają dokładnie tej samej składni co poprzednio; po prostu używają nowego słowa kluczowego, które działa jak typ, ale ma specjalne znaczenie. To właśnie pozwala mu działać w oparciu o zakres fori odliczanie typu zwracanego. To właśnie pozwala mu mieć wiele form ( autovs. decltype(auto)). I tak dalej.

Symbole zastępcze działają, ponieważ są najprostszym rozwiązaniem problemu, zachowując jednocześnie wszystkie korzyści i ogólność używania rzeczywistej nazwy typu. Jeśli wpadłeś na inną alternatywę, która działała tak uniwersalnie, jak symbole zastępcze, jest bardzo mało prawdopodobne, że byłaby tak prosta, jak symbole zastępcze.

Chyba że była to tylko pisownia symboli zastępczych z różnymi słowami kluczowymi lub symbolami ...

Nicol Bolas
źródło
2
IMHO, to jedyna odpowiedź, która dotyczy jakiegoś istotnego uzasadnienia wyboru symbolu zastępczego. Innym przykładem może być dedukcja typów w ogólnej lambdzie. Szkoda, że ​​ta odpowiedź otrzymała tak mało głosów pozytywnych tylko dlatego, że została opublikowana trochę późno ...
llllllllll
@liliscent: „ Innym przykładem może być dedukcja typu w ogólnej lambdzie” . Nie wspomniałem o tym, ponieważ ma inną semantykę niż autodeklaracje / odliczenie wartości zwracanej.
Nicol Bolas
@liliscent: Rzeczywiście, ta odpowiedź jest spóźniona na przyjęcie. Podniesiony. (Jednym ulepszeniem byłaby jednak wzmianka o idei C ++, że jeśli coś może być deklaracją, to jest to deklaracja.)
Batszeba
12

Krótko mówiąc: autow niektórych przypadkach można by odrzucić, ale prowadziłoby to do niespójności.

Przede wszystkim, jak wskazano, składnia deklaracji w C ++ to <type> <varname>. Jawne deklaracje wymagają w jego miejsce słowa kluczowego typu lub przynajmniej deklaracji. Moglibyśmy więc użyć var <varname>lub declare <varname>czegoś podobnego, ale autojest to od dawna słowo kluczowe w C ++ i jest dobrym kandydatem na słowo kluczowe automatycznej dedukcji typu.

Czy można niejawnie zadeklarować zmienne przez przypisanie bez zrywania wszystkiego?

Czasem tak. Nie możesz wykonać przypisania poza funkcjami, więc możesz użyć składni przypisania dla tamtejszych deklaracji. Ale takie podejście wprowadziłoby niespójność w języku, prawdopodobnie prowadząc do błędów ludzkich.

a = 0; // Error. Could be parsed as auto declaration instead.
int main() {
  return 0;
}

A jeśli chodzi o wszelkiego rodzaju zmienne lokalne, jawne deklaracje są sposobem kontrolowania zakresu zmiennej.

a = 1; // use a variable declared before or outside
auto b = 2; // declare a variable here

Gdyby dozwolona była niejednoznaczna składnia, deklarowanie zmiennych globalnych mogłoby nagle przekształcić lokalne niejawne deklaracje w przypisania. Znalezienie tych konwersji wymagałoby sprawdzenia wszystkiego . Aby uniknąć kolizji, potrzebowałbyś unikalnych nazw dla wszystkich globali, co w pewnym sensie niweczy całą ideę określania zakresu. Więc jest naprawdę źle.

Andrew Svietlichnyy
źródło
11

autojest słowem kluczowym, którego możesz użyć w miejscach, w których zwykle musisz określić typ .

  int x = some_function();

Można uczynić bardziej ogólnym, intautomatycznie określając typ:

  auto x = some_function();

Jest to więc konserwatywne rozszerzenie języka; pasuje do istniejącej składni. Bez niej x = some_function()staje się instrukcją przypisania, a nie deklaracją.

rustyx
źródło
9

składnia musi być jednoznaczna, a także kompatybilna wstecz.

Jeśli porzucisz auto, nie będzie możliwości rozróżnienia między instrukcjami a definicjami.

auto n = 0; // fine
n=0; // statememt, n is undefined.
code707
źródło
3
Ważną kwestią jest to, że autobyło to już słowo kluczowe (ale o przestarzałym znaczeniu), więc nie złamało kodu, używając go jako nazwy. Z tego powodu nie wybrano zamiast tego lepszego słowa kluczowego, takiego jak varlub let.
Frax
1
@Frax IMO autojest w rzeczywistości całkiem doskonałym słowem kluczowym do tego celu: wyraża dokładnie to, co oznacza, mianowicie zastępuje nazwę typu słowem „typ automatyczny”. W przypadku słowa kluczowego takiego jak varlub let, należy konsekwentnie wymagać słowa kluczowego, nawet jeśli typ jest określony jawnie, tj. var int n = 0Lub coś podobnego var n:Int = 0. Tak to się w zasadzie robi w Rust.
lewej około
1
@leftaroundabout Chociaż autojest zdecydowanie doskonała w kontekście istniejącej składni, powiedziałbym, że coś w rodzaju var int x = 42bycia podstawową definicją zmiennej, ze skrótami var x = 42i int x = 42jako skróty, miałoby więcej sensu niż obecna składnia, jeśli rozpatruje się ją poza treścią historyczną. Ale to głównie kwestia gustu. Ale masz rację, powinienem był napisać „jeden z powodów” zamiast „powodu” w moim oryginalnym komentarzu :)
Frax
@leftaroundabout: "auto jest właściwie całkiem doskonałym słowem kluczowym do tego: wyraża dokładnie to, co oznacza, mianowicie zastępuje nazwę typu" typem automatycznym "" To nie jest prawda. Nie ma „typu automatycznego”.
Wyścigi lekkości na orbicie
@LightnessRacesinOrbit w dowolnym kontekście, w którym można użyć auto, istnieje typ automatyczny (inny, w zależności od wyrażenia).
lewej około
3

Dodając do poprzednich odpowiedzi jedną dodatkową notatkę ze starego pierdnięcia: Wygląda na to, że możesz uznać to za zaletę, aby móc po prostu zacząć używać nowej zmiennej bez jej deklarowania.

W językach z możliwością niejawnej definicji zmiennych może to stanowić duży problem, szczególnie w większych systemach. Popełniasz jedną literówkę i debugujesz godzinami tylko po to, aby dowiedzieć się, że nieumyślnie wprowadziłeś zmienną o wartości zero (lub gorszej) - bluevs bleu, labelvs lable... w rezultacie nie możesz naprawdę ufać żadnemu kodowi bez dokładnego sprawdzenia dokładności nazwy zmiennych.

Samo użycie automówi zarówno kompilatorowi, jak i opiekunowi, że Twoim zamiarem jest zadeklarowanie nowej zmiennej.

Pomyśl o tym, aby móc uniknąć tego rodzaju koszmarów, w FORTRANIE zostało wprowadzone stwierdzenie „implicit none” - i widzisz, że jest ono używane we wszystkich poważnych programach FORTRAN obecnie. Brak tego jest po prostu ... przerażający.

Bert Bril
źródło