W lekcji 10.8 -- Dedukcja typu dla obiektów korzystających z auto słowo kluczowe, omówiliśmy, jak można użyć słowa kluczowego auto , aby kompilator wywnioskował typ zmiennej z inicjatora:
int main()
{
int a { 5 };
auto b { a }; // b deduced as an int
return 0;
}Zauważyliśmy również, że poprzez domyślnie dedukcja typu zostanie usunięta const z typów:
int main()
{
const double a { 7.8 }; // a has type const double
auto b { a }; // b has type double (const dropped)
constexpr double c { 7.8 }; // c has type const double (constexpr implicitly applies const)
auto d { c }; // d has type double (const dropped)
return 0;
}Const (lub constexpr) można zastosować ponownie, dodając kwalifikator const (lub constexpr) do definicji wydedukowanego typu:
int main()
{
double a { 7.8 }; // a has type double
const auto b { a }; // b has type const double (const applied)
constexpr double c { 7.8 }; // c has type const double (constexpr implicitly applies const)
const auto d { c }; // d is const double (const dropped, const reapplied)
constexpr auto e { c }; // e is constexpr double (const dropped, constexpr reapplied)
return 0;
}Dedukcja typu usuwa odniesienia
Oprócz usunięcia const, dedukcja typu spowoduje również usunięcie referencji:
#include <string>
std::string& getRef(); // some function that returns a reference
int main()
{
auto ref { getRef() }; // type deduced as std::string (not std::string&)
return 0;
}W powyższym przykładzie zmienna ref stosuje dedukcję typu. Chociaż funkcja getRef() zwraca a std::string&, kwalifikator odniesienia jest odrzucany, więc typ ref jest wydedukowany jako std::string.
Podobnie jak w przypadku usuniętego const, jeśli chcesz, aby wydedukowany typ był odniesieniem, możesz ponownie zastosować odniesienie w punkcie definicji:
#include <string>
std::string& getRef(); // some function that returns a reference
int main()
{
auto ref1 { getRef() }; // std::string (reference dropped)
auto& ref2 { getRef() }; // std::string& (reference dropped, reference reapplied)
return 0;
}Stały najwyższy poziom i niski poziom const
A najwyższego poziomu const jest kwalifikatorem const, który ma zastosowanie do samego obiektu. Na przykład:
const int x; // this const applies to x, so it is top-level
int* const ptr; // this const applies to ptr, so it is top-level
// references don't have a top-level const syntax, as they are implicitly top-level constZ kolei stała niskiego poziomu jest kwalifikatorem stałej, który ma zastosowanie do obiektu, do którego odwołuje się lub na który wskazuje:
const int& ref; // this const applies to the object being referenced, so it is low-level
const int* ptr; // this const applies to the object being pointed to, so it is low-levelOdwołanie do wartości stałej jest zawsze stałą niskiego poziomu. Wskaźnik może mieć stałą najwyższego, niskiego poziomu lub oba rodzaje stałych:
const int* const ptr; // the left const is low-level, the right const is top-levelKiedy mówimy, że dedukcja typu pomija kwalifikatory const, pomija tylko stałe najwyższego poziomu. Stałe niskiego poziomu nie są usuwane. Za chwilę zobaczymy tego przykłady.
Dedukcja typu i odniesienia do stałych
Jeśli inicjatorem jest odwołanie do const, odwołanie jest najpierw usuwane (a następnie stosowane ponownie, jeśli ma to zastosowanie), a następnie z wyniku usuwane są wszelkie stałe najwyższego poziomu.
#include <string>
const std::string& getConstRef(); // some function that returns a reference to const
int main()
{
auto ref1{ getConstRef() }; // std::string (reference dropped, then top-level const dropped from result)
return 0;
}W powyższym przykładzie, ponieważ getConstRef() zwraca a const std::string&, odwołanie jest usuwane jako pierwsze, pozostawiając nas z a const std::string. Ta stała jest teraz stałą najwyższego poziomu, więc została również usunięta, pozostawiając wydedukowany typ jako std::string.
Kluczowa informacja
Usunięcie odniesienia może zmienić stałą niskiego poziomu na stałą najwyższego poziomu: const std::string& jest stałą niskiego poziomu, ale porzuca wydajność odniesienia const std::string, która jest stałą najwyższego poziomu.
Możemy ponownie zastosować odniesienie i/lub const:
#include <string>
const std::string& getConstRef(); // some function that returns a const reference
int main()
{
auto ref1{ getConstRef() }; // std::string (reference and top-level const dropped)
const auto ref2{ getConstRef() }; // const std::string (reference dropped, const dropped, const reapplied)
auto& ref3{ getConstRef() }; // const std::string& (reference dropped and reapplied, low-level const not dropped)
const auto& ref4{ getConstRef() }; // const std::string& (reference dropped and reapplied, low-level const not dropped)
return 0;
}Przypadek ref1 omówiliśmy w poprzednim przykładzie. W przypadku ref2 jest to podobne do przypadku ref1 , z tą różnicą, że ponownie stosujemy kwalifikator const , więc wydedukowany typ to const std::string.
Rzeczy stają się bardziej interesujące z ref3. Zwykle odniesienie zostałoby najpierw usunięte, ale ponieważ ponownie je zastosowaliśmy, nie jest ono usuwane. Oznacza to, że typem jest nadal const std::string&. A ponieważ ta stała jest stałą niskiego poziomu, nie jest ona usuwana. Zatem wydedukowany typ to const std::string&.
Klasa ref4 działa podobnie do przypadku ref3, z tą różnicą, że ponownie zastosowaliśmy również kwalifikator const . Ponieważ typ został już wydedukowany jako odniesienie do const, ponowne zastosowanie const w tym miejscu jest zbędne. To powiedziawszy, użycie const w tym miejscu wyraźnie pokazuje, że nasz wynik będzie stały (podczas gdy w przypadku ref3 stałość wyniku jest ukryta i nieoczywista).
Najlepsza praktyka
Jeśli chcesz odniesienia do stałej, zastosuj ponownie kwalifikator const nawet jeśli nie jest to absolutnie konieczne, ponieważ wyjaśnia to Twoje intencje i pomaga zapobiegać błędy.
A co z odniesieniami do constexpr?
Constexpr nie jest częścią typu wyrażenia, więc nie jest wywnioskowane przez auto.
Przypomnienie
Podczas definiowania odniesienia do const (np. const int&), const odnosi się do obiektu, do którego się odwołuje, a nie do samego odniesienia.
Podczas definiowania odwołania constexpr do zmiennej const (np. constexpr const int&), musimy zastosować oba constexpr (co dotyczy odniesienia), jak i const (co dotyczy typu, do którego się odwołujemy).
Jest to omówione w lekcji 12.4 -- Odniesienia do wartości do const.
#include <string_view>
#include <iostream>
constexpr std::string_view hello { "Hello" }; // implicitly const
constexpr const std::string_view& getConstRef() // function is constexpr, returns a const std::string_view&
{
return hello;
}
int main()
{
auto ref1{ getConstRef() }; // std::string_view (reference dropped and top-level const dropped)
constexpr auto ref2{ getConstRef() }; // constexpr const std::string_view (reference dropped and top-level const dropped, constexpr applied, implicitly const)
auto& ref3{ getConstRef() }; // const std::string_view& (reference reapplied, low-level const not dropped)
constexpr const auto& ref4{ getConstRef() }; // constexpr const std::string_view& (reference reapplied, low-level const not dropped, constexpr applied)
return 0;
}Dukcja typu i wskaźniki
W przeciwieństwie do odniesień, dedukcja typu nie powoduje utraty wskaźników:
#include <string>
std::string* getPtr(); // some function that returns a pointer
int main()
{
auto ptr1{ getPtr() }; // std::string*
return 0;
}Możemy również użyć gwiazdki w połączeniu z dedukcją typu wskaźnika (auto*), aby było jaśniejsze, że wydedukowany typem jest wskaźnik:
#include <string>
std::string* getPtr(); // some function that returns a pointer
int main()
{
auto ptr1{ getPtr() }; // std::string*
auto* ptr2{ getPtr() }; // std::string*
return 0;
}Kluczowa informacja
Powodem, dla którego referencje są odrzucane podczas dedukcji typu, ale wskaźniki nie są usuwane, jest to, że referencje i wskaźniki mają różną semantykę.
Kiedy oceniamy referencję, tak naprawdę oceniamy obiekt, do którego się odwołujemy. Dlatego też, dedukując typ, sensowne jest, abyśmy wydedukowali typ rzeczy, do której się odwołujemy, a nie samo odniesienie. Ponadto, ponieważ wydedukowaliśmy brak odniesienia, bardzo łatwo jest uczynić z niego odniesienie, używając auto&. Jeśli zamiast tego dedukcja typu miałaby wydedukować referencję, składnia usuwania referencji, jeśli tego nie chcieliśmy, byłaby znacznie bardziej skomplikowana.
Z drugiej strony wskaźniki przechowują adres obiektu. Kiedy oceniamy wskaźnik, oceniamy wskaźnik, a nie obiekt, na który wskazujemy (jeśli chcemy, możemy wyłuskać wskaźnik). Dlatego sensowne jest, abyśmy wydedukowali typ wskaźnika, a nie rzecz, na którą wskazuje.
Różnica między auto i auto* Opcjonalne
Kiedy używamy auto z inicjatorem typu wskaźnika, typ wydedukowany dla auto obejmuje wskaźnik. Tak więc dla ptr1 powyżej typ zostaje zastąpiony auto Jest std::string*.
Kiedy używamy auto* z inicjatorem typu wskaźnikowego, typ wydedukowany dla auto nie obejmuje wskaźnik - wskaźnik jest później stosowany ponownie po wydedukowaniu typu. Zatem w ptr2 powyżej typ zostaje zastąpiony auto Jest std::string, a następnie wskaźnik jest ponownie stosowany.
W większości przypadków praktyczny efekt jest taki sam (ptr1 i ptr2 oba wnioski wynikają z std::string* w powyższym przykładzie).
Jest jednak kilka różnic pomiędzy auto i auto* w praktyce. Po pierwsze, auto* musi zostać przekształcony w inicjator wskaźnika, w przeciwnym razie wystąpi błąd kompilacji:
#include <string>
std::string* getPtr(); // some function that returns a pointer
int main()
{
auto ptr3{ *getPtr() }; // std::string (because we dereferenced getPtr())
auto* ptr4{ *getPtr() }; // does not compile (initializer not a pointer)
return 0;
}Ma to sens: w ptr4 przypadku auto wydedukuje się do std::string, a następnie wskaźnik zostanie ponownie zastosowany. Zatem ptr4 ma typ std::string* i nie możemy zainicjować std::string* inicjatorem, który nie jest wskaźnikiem.
Po drugie, istnieją różnice w auto i auto* zachowaniu, gdy wprowadzamy const do równania. Omówimy to poniżej.
Wpisz dedukcję i wskaźniki const Opcjonalne
Ponieważ wskaźniki nie są usuwane, nie musimy się tym martwić. Ale w przypadku wskaźników mamy do przemyślenia zarówno wskaźnik const, jak i wskaźnik do przypadków const, a także mamy auto vs auto*. Podobnie jak w przypadku referencji, podczas dedukcji typu wskaźnika pomijana jest tylko stała najwyższego poziomu.
Zacznijmy od prostego przypadku:
#include <string>
std::string* getPtr(); // some function that returns a pointer
int main()
{
const auto ptr1{ getPtr() }; // std::string* const
auto const ptr2 { getPtr() }; // std::string* const
const auto* ptr3{ getPtr() }; // const std::string*
auto* const ptr4{ getPtr() }; // std::string* const
return 0;
}Kiedy używamy jednego z auto const lub const auto, mówimy: „uczyń wydedukowany wskaźnik wskaźnikiem stałym”. Zatem w przypadku ptr1 i ptr2 wydedukowanym typem jest std::string*, a następnie stosowana jest const, co daje ostateczny typ std::string* const. Jest to podobne do tego, jak const int i int const oznacza to samo.
Jednak gdy używamy auto*, kolejność kwalifikatora stałej ma znaczenie. A const po lewej stronie oznacza „uczyń wydedukowany wskaźnik wskaźnikiem do stałej”, podczas gdy a const po prawej oznacza „uczyń wydedukowany wskaźnik wskaźnikiem stałym”. Zatem ptr3 kończy jako wskaźnik do const i ptr4 jest wskaźnikiem const.
Przyjrzyjmy się teraz przykładowi, w którym inicjatorem jest wskaźnik const do const.
#include <string>
int main()
{
std::string s{};
const std::string* const ptr { &s };
auto ptr1{ ptr }; // const std::string*
auto* ptr2{ ptr }; // const std::string*
auto const ptr3{ ptr }; // const std::string* const
const auto ptr4{ ptr }; // const std::string* const
auto* const ptr5{ ptr }; // const std::string* const
const auto* ptr6{ ptr }; // const std::string*
const auto const ptr7{ ptr }; // error: const qualifer can not be applied twice
const auto* const ptr8{ ptr }; // const std::string* const
return 0;
}Klasa ptr1 i ptr2 przypadki są proste. Stała najwyższego poziomu (stała na samym wskaźniku) jest usuwana. Stała niskiego poziomu na wskazywanym obiekcie nie jest usuwana. Zatem w obu przypadkach ostateczny typ const std::string*.
Klasa ptr3 i ptr4 przypadki są również proste. Stała najwyższego poziomu została usunięta, ale zastosujemy ją ponownie. Stała niskiego poziomu na wskazywanym obiekcie nie jest usuwana. Zatem w obu przypadkach ostateczny typ to const std::string* const.
Klasa ptr5 i ptr6 przypadki są analogiczne do przypadków, które pokazaliśmy w poprzednim przykładzie. W obu przypadkach stała najwyższego poziomu jest odrzucana. For ptr5, instrukcja auto* const ponownie stosuje stałą najwyższego poziomu, więc ostatecznym typem jest const std::string* const. Instancja for ptr6, instrukcja const auto* stosuje const do wskazywanego typu (który w tym przypadku był już const), więc końcowym typem jest const std::string*.
W ptr7 w przypadku <<<M0>>>stosujemy kwalifikator const dwukrotnie, co jest niedozwolone i spowoduje błąd kompilacji.
I na koniec, w przypadku ptr8 stosujemy const po obu stronach wskaźnika (co jest dozwolone, ponieważ auto* musi być typem wskaźnika), więc wynikowy typ to const std::string* const.
Najlepsza praktyka
Jeśli chcesz wskaźnik const, wskaźnik do const lub wskaźnik const do const, zastosuj ponownie const kwalifikator(y), nawet jeśli nie jest to absolutnie konieczne, ponieważ wyjaśnia to intencje i pomaga zapobiegać błędom.
Wskazówka
Rozważ użycie auto* przy dedukowaniu typu wskaźnika. Użycie auto* w tym przypadku wyjaśnia, że dedukujemy typ wskaźnika, korzysta z pomocy kompilatora, aby upewnić się, że nie wydedukujemy typu innego niż wskaźnik, i daje większą kontrolę nad const.
Streszczenie
Przykro nam z powodu bólu głowy. Podsumujmy szybko najważniejsze punkty.
Stała najwyższego poziomu a stała niskiego poziomu:
- Stała najwyższego poziomu dotyczy samego obiektu (np.
const int xlubint* const ptr). - Stała niskiego poziomu dotyczy obiektu, do którego można uzyskać dostęp poprzez referencję lub wskaźnik (np.
const int& ref,const int* ptr).
Jaki rodzaj dedukcji dedukuje:
- Typ dedukcji najpierw usuwa wszelkie referencje (chyba że wywnioskowany typ jest zdefiniowany jako odniesienie). W przypadku stałego odniesienia, usunięcie odniesienia spowoduje, że stała (niskiego poziomu) stanie się stałą najwyższego poziomu.
- Dedukcja typu następnie usuwa wszelkie stałe najwyższego poziomu (chyba że wydedukowany typ jest zdefiniowany jako
constlubconstexpr). - Constexpr nie jest częścią systemu typów, więc nigdy nie jest wydedukowany. Zawsze musi być jawnie zastosowany do wydedukowanego typ.
- Dedukcja typu nie powoduje utraty wskaźników.
- Zawsze jawnie definiuj wydedukowany typ jako odniesienie,
constlubconstexpr(jeśli ma to zastosowanie), nawet jeśli te kwalifikatory są zbędne, ponieważ mogłyby zostać wydedukowane. Pomaga to uniknąć błędów i wyjaśnia, jaki jest Twój zamiar.
Wpisz dedukcję i wskaźniki:
- Podczas używania
auto, typ wydedukowany będzie wskaźnikiem tylko wtedy, gdy inicjatorem jest wskaźnik. W przypadku użyciaauto*typ wydedukowany jest zawsze wskaźnikiem, nawet jeśli inicjatorem nie jest wskaźnik. auto consticonst autooba powodują, że wydedukowany wskaźnik jest wskaźnikiem stałym. Nie ma możliwości jawnego określenia stałej niskiego poziomu (wskaźnik do stałej) za pomocąauto.auto* constrównież powoduje wydedukowany wskaźnik. wskaźnik stałej.const auto*czyni z wydedukowanego wskaźnika wskaźnik do stałej. Jeśli są one trudne do zapamiętania,int* constjest wskaźnikiem stałej (do int), więcauto* constmusi być wskaźnikiem stałej.const int*jest wskaźnikiem do stałej (int), więcconst auto*musi być wskaźnikiem pointer-to-const)- Rozważ użycie
auto*zaautopodczas dedukcji typu wskaźnika, ponieważ pozwala to na jawne ponowne zastosowanie zarówno stałej najwyższego, jak i niskiego poziomu, i spowoduje błąd, jeśli typ wskaźnika nie zostanie wydedukowany.

