Wraz z wydaniem GCC 4.8.0 mamy kompilator obsługujący automatyczne odejmowanie zwracanych typów, część C ++ 14. Dzięki -std=c++1y
, mogę to zrobić:
auto foo() { //deduced to be int
return 5;
}
Moje pytanie brzmi: kiedy powinienem używać tej funkcji? Kiedy jest to konieczne i kiedy powoduje, że kod staje się czystszy?
Scenariusz 1
Pierwszy scenariusz, jaki przychodzi mi do głowy, jest możliwy, kiedy tylko jest to możliwe. Każda funkcja, którą można w ten sposób zapisać, powinna być. Problem polega na tym, że nie zawsze może to uczynić kod bardziej czytelnym.
Scenariusz 2
Następnym scenariuszem jest uniknięcie bardziej złożonych typów zwrotów. Jako bardzo lekki przykład:
template<typename T, typename U>
auto add(T t, U u) { //almost deduced as decltype(t + u): decltype(auto) would
return t + u;
}
Nie wierzę, że kiedykolwiek byłby to naprawdę problem, chociaż wydaje mi się, że posiadanie typu zwracanego jawnie zależnego od parametrów może być w niektórych przypadkach wyraźniejsze.
Scenariusz 3
Następnie, aby zapobiec redundancji:
auto foo() {
std::vector<std::map<std::pair<int, double>, int>> ret;
//fill ret in with stuff
return ret;
}
W C ++ 11 możemy czasami po prostu return {5, 6, 7};
zastąpić wektor, ale to nie zawsze działa i musimy określić typ zarówno w nagłówku funkcji, jak i jej treści. Jest to całkowicie nadmiarowe, a automatyczne odliczanie typu zwracanego chroni nas przed tą nadmiarowością.
Scenariusz 4
Wreszcie może być używany zamiast bardzo prostych funkcji:
auto position() {
return pos_;
}
auto area() {
return length_ * width_;
}
Czasami jednak możemy spojrzeć na funkcję, chcąc poznać dokładny typ, a jeśli nie jest tam podany, musimy przejść do innego punktu w kodzie, np. Gdzie pos_
jest zadeklarowane.
Wniosek
Po przedstawieniu tych scenariuszy, który z nich faktycznie okazuje się sytuacją, w której ta funkcja jest przydatna w oczyszczaniu kodu? A co ze scenariuszami, o których tu zapomniałem? Jakie środki ostrożności należy podjąć przed użyciem tej funkcji, aby później mnie nie ugryzła? Czy jest coś nowego, co ta funkcja wnosi do stołu, a bez niej nie jest możliwe?
Zwróć uwagę, że wiele pytań ma być pomocą w znalezieniu perspektyw, z których można na to odpowiedzieć.
źródło
->decltype(t+u)
autodliczeniem zabija SFINAE.Odpowiedzi:
C ++ 11 rodzi podobne pytania: kiedy używać dedukcji typu zwracanego w lambdach, a kiedy
auto
zmiennych.Tradycyjna odpowiedź na pytanie w C i C ++ 03 brzmiała: „ponad granicami instrukcji uczynimy typy jawnymi, w wyrażeniach zazwyczaj są one niejawne, ale możemy uczynić je jawnymi za pomocą rzutowania”. C ++ 11 i C ++ 1y wprowadzają narzędzia do odliczania typów, dzięki czemu można pominąć typ w nowych miejscach.
Przepraszamy, ale nie rozwiążesz tego z góry, ustalając ogólne zasady. Musisz przyjrzeć się konkretnemu kodowi i samemu zdecydować, czy poprawi on czytelność określania typów w każdym miejscu: czy lepiej jest, aby twój kod mówił „typem tego jest X”, czy lepiej dla Twój kod mówiący: „rodzaj tego nie ma znaczenia dla zrozumienia tej części kodu: kompilator musi wiedzieć i prawdopodobnie moglibyśmy to rozwiązać, ale nie musimy tego tutaj mówić”?
Ponieważ „czytelność” nie jest obiektywnie zdefiniowana [*], a ponadto różni się w zależności od czytelnika, jako autor / redaktor fragmentu kodu nie można w pełni zadowolić się przewodnikiem po stylu. Nawet jeśli przewodnik po stylu określa normy, różni ludzie będą preferować inne normy i będą mieli tendencję do stwierdzania, że coś nieznanego jest „mniej czytelne”. Tak więc czytelność określonej proponowanej reguły stylu można często ocenić tylko w kontekście innych obowiązujących reguł stylu.
Wszystkie Twoje scenariusze (nawet pierwszy) znajdą zastosowanie w czyimś stylu kodowania. Osobiście uważam, że drugi przypadek jest najbardziej przekonujący, ale mimo to spodziewam się, że będzie to zależało od narzędzi do dokumentacji. Nie jest zbyt pomocne, aby zobaczyć udokumentowany typ zwracanego szablonu funkcji
auto
, podczas gdy zobaczenie, że jest on udokumentowany jakodecltype(t+u)
tworzy opublikowany interfejs, na którym możesz (miejmy nadzieję) polegać.[*] Czasami ktoś próbuje dokonać obiektywnych pomiarów. W niewielkim stopniu, w jakim ktokolwiek wymyśli jakiekolwiek statystycznie istotne i ogólnie nadające się do zastosowania wyniki, są one całkowicie ignorowane przez pracujących programistów na rzecz instynktu autora co do tego, co jest „czytelne”.
źródło
1.0 + 27U
, potwierdzamy to drugie, kiedy piszemy(double)1.0 + (double)27U
, potwierdzamy to pierwsze. Prostota funkcji, stopień powielania, unikaniedecltype
mogą się do tego przyczynić, ale żadna z nich nie będzie decydująca.auto
ogólnie.auto
słowem kluczowym, wyświetli rzeczywisty zwracany typ.int*
, ale to, co jest naprawdę istotne, jeśli cokolwiek, to to, że powodem jestint*
to, że takstd::vector<int>::iterator_type
jest z twoimi obecnymi opcjami kompilacji!Ogólnie rzecz biorąc, zwracany typ funkcji jest bardzo pomocny w dokumentowaniu funkcji. Użytkownik będzie wiedział, czego się oczekuje. Jest jednak jeden przypadek, w którym myślę, że fajnie byłoby porzucić ten typ powrotu, aby uniknąć nadmiarowości. Oto przykład:
Ten przykład pochodzi z oficjalnego dokumentu komisji N3493 . Celem funkcji
apply
jest przekazanie elementów astd::tuple
do funkcji i zwrócenie wyniku.int_seq
Amake_int_seq
są jedynie częścią realizacji i będzie prawdopodobnie tylko mylić każdy użytkownik próbuje zrozumieć, co robi.Jak widać, zwracany typ to nic innego jak
decltype
zwrócone wyrażenie. Co więcej,apply_
nie będąc przeznaczonym dla użytkowników, nie jestem pewien przydatności dokumentowania jego typu zwracanego, gdy jest mniej więcej taki sam jakapply
ten. Myślę, że w tym konkretnym przypadku porzucenie zwracanego typu sprawia, że funkcja jest bardziej czytelna. Zwróć uwagę, że ten właśnie zwracany typ został faktycznie usunięty i zastąpiony przezdecltype(auto)
propozycję dodaniaapply
do standardu N3915 (zwróć również uwagę, że moja oryginalna odpowiedź pochodzi sprzed tego artykułu):Jednak w większości przypadków lepiej jest zachować ten typ zwracania. W konkretnym przypadku, który opisałem powyżej, typ zwracany jest raczej nieczytelny i potencjalny użytkownik nic nie zyska na jego znajomości. O wiele bardziej przydatna będzie dobra dokumentacja z przykładami.
Kolejna rzecz, o której jeszcze nie wspomniano: podczas
declype(t+u)
pozwala na użycie wyrażenia SFINAE ,decltype(auto)
nie (mimo że jest propozycja zmiany tego zachowania). Weźmy na przykładfoobar
funkcję, która wywołafoo
funkcjębar
składową typu, jeśli istnieje,foo
lub wywoła funkcję składową typu, jeśli istnieje, i załóż, że klasa zawsze ma dokładność lubbar
nie ma obu jednocześnie:Wywołanie
foobar
wystąpieniaX
will displayfoo
podczas wywołaniafoobar
wystąpieniaY
will displaybar
. Jeśli zamiast tego użyjesz automatycznego odliczenia typu zwracanego (z lub bezdecltype(auto)
), nie otrzymasz wyrażenia SFINAE i wywołaniefoobar
wystąpienia któregokolwiek z nichX
lubY
wyzwoli błąd w czasie kompilacji.źródło
To nigdy nie jest konieczne. Jeśli chodzi o to, kiedy powinieneś - otrzymasz wiele różnych odpowiedzi na ten temat. Powiedziałbym, że nie, dopóki nie będzie to faktycznie akceptowana część standardu i dobrze obsługiwana przez większość głównych kompilatorów w ten sam sposób.
Poza tym będzie to argument religijny. Osobiście powiedziałbym nigdy - wprowadzenie rzeczywistego typu zwracanego kodu sprawia, że kod jest jaśniejszy, jest znacznie łatwiejszy w utrzymaniu (mogę spojrzeć na podpis funkcji i wiedzieć, co zwraca, zamiast czytać kod) i eliminuje możliwość, że myślisz, że powinien zwrócić jeden typ, a kompilator uważa, że inny powoduje problemy (tak jak stało się z każdym językiem skryptowym, którego kiedykolwiek używałem). Myślę, że auto było gigantycznym błędem i spowoduje o rząd wielkości więcej bólu niż pomocy. Inni powiedzą, że powinieneś go używać przez cały czas, ponieważ pasuje to do ich filozofii programowania. W każdym razie jest to poza zakresem tej witryny.
źródło
#define
s do przekształcenia C ++ w VB. Funkcje generalnie są zgodne z mentalnością języka co do tego, co jest właściwe, a co nie, zgodnie z tym, do czego są przyzwyczajeni programiści tego języka. Ta sama funkcja może być dostępna w wielu językach, ale każdy ma własne wytyczne dotyczące jej używania.auto
to czyste błogosławieństwo. Usuwa wiele nadmiarowości. Czasami powtarzanie typu powrotu jest po prostu uciążliwe. Jeśli chcesz zwrócić lambdę, może to być nawet niemożliwe bez zapisania wyniku w astd::function
, co może wiązać się z pewnym narzutem.auto
aby zawsze był zgodny z typem zwracanym przekazanej funkcji.auto
dla końcowego typu powrotu.]Nie ma to nic wspólnego z prostotą funkcji (jak przypuszcza się usunięty teraz duplikat tego pytania).
Typ zwracany jest stały (nie używaj
auto
) lub zależy w złożony sposób od parametru szablonu (używajauto
w większości przypadków w połączeniu zdecltype
wieloma punktami powrotu).źródło
Rozważmy rzeczywiste środowisko produkcyjne: wiele funkcji i testów jednostkowych jest współzależnych od zwracanego typu
foo()
. Załóżmy teraz, że typ zwracany musi się zmienić z jakiegokolwiek powodu.Jeśli typ zwracany jest
auto
wszędzie, a osoby wywołującefoo()
i powiązane funkcje używająauto
zwracanej wartości, zmiany, które należy wprowadzić, są minimalne. Jeśli nie, może to oznaczać godziny wyjątkowo żmudnej i podatnej na błędy pracy.Jako przykład ze świata rzeczywistego poproszono mnie o zmianę modułu z używania wszędzie wskaźników surowych na inteligentne wskaźniki. Naprawianie testów jednostkowych było bardziej bolesne niż sam kod.
Chociaż istnieją inne sposoby rozwiązania tego problemu, użycie
auto
typów zwracanych wydaje się być dobrym rozwiązaniem.źródło
decltype(foo())
?typedef
deklaracje s i aliasów (tj.using
Deklaracja typu) są lepsze w takich przypadkach, szczególnie gdy mają zakres klas.Chcę podać przykład, w którym auto zwracanego typu jest idealne:
Wyobraź sobie, że chcesz utworzyć krótki alias dla długiego kolejnego wywołania funkcji. Dzięki auto nie musisz dbać o oryginalny typ zwrotu (być może zmieni się w przyszłości), a użytkownik może kliknąć oryginalną funkcję, aby uzyskać prawdziwy typ zwrotu:
PS: Zależy od tego pytania.
źródło
W scenariuszu 3 zamieniłbym zwracany typ podpisu funkcji na zwracaną zmienną lokalną. Uczyniłoby to bardziej zrozumiałym dla programistów-klientów, kiedy funkcja zwraca. Lubię to:
Scenariusz 3 Aby zapobiec redundancji:
Tak, nie ma słowa kluczowego auto, ale zasada jest taka sama, aby zapobiec redundancji i dać łatwiejszy czas programistom, którzy nie mają dostępu do źródła.
źródło
vector<map<pair<int,double>,int>
a następnie użycie tego.