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 name
nie 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 identifier
nastę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, auto
zawsze 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: auto
idecltype(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 for
pę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 for
się 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 for
i odliczanie typu zwracanego. To właśnie pozwala mu mieć wiele form ( auto
vs. 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 ...