Wiemy, że „zmienna stała” oznacza, że raz przypisana nie można zmienić zmiennej, na przykład:
int const i = 1;
i = 2;
Powyższy program nie skompiluje się; gcc wyświetla komunikat z błędem:
assignment of read-only variable 'i'
Nie ma problemu, rozumiem to, ale następujący przykład jest poza moim zrozumieniem:
#include<iostream>
using namespace std;
int main()
{
boolalpha(cout);
int const i = 1;
cout << is_const<decltype(i)>::value << endl;
int const &ri = i;
cout << is_const<decltype(ri)>::value << endl;
return 0;
}
Wyprowadza
true
false
Dziwne. Wiemy, że gdy odniesienie jest powiązane z nazwą / zmienną, nie możemy zmienić tego powiązania, zmieniamy związany z nim obiekt. Więc przypuszczam, że typ ri
powinien być taki sam, jak i
: kiedy i
jest int const
, dlaczego ri
nie const
?
boolalpha(cout)
bardzo nietypowy. Możeszstd::cout << boolalpha
zamiast tego zrobić .ri
bycie „aliasem”, którego nie da się odróżnići
.i
jest również odniesieniem, ale ze względów historycznych nie deklarujesz go jako takiego w wyraźny sposób. Tak więci
jest to odniesienie, które odnosi się do magazynu iri
jest odniesieniem, które odnosi się do tego samego magazynu. Ale nie ma różnicy w naturze pomiędzyi
ari
. Ponieważ nie można zmienić powiązania odniesienia, nie ma potrzeby kwalifikowania go jakoconst
. I pozwól mi powiedzieć, że komentarz @Kaz jest znacznie lepszy niż zweryfikowana odpowiedź (nigdy nie wyjaśniaj odwołań za pomocą wskaźników, ref to nazwa, a ptr to zmienna).is_const
się powrotu równieżtrue
w tym przypadku. Moim zdaniem jest to dobry przykład tego, dlaczegoconst
jest zasadniczo zacofany; „zmienny” atrybut (a la Rustamut
) wydaje się, że byłby bardziej spójny.is_const<int const &>::value
fałszywy?” lub podobne; Staram się dostrzec jakiekolwiek znaczenie tego pytania poza pytaniem o zachowanie cech typuOdpowiedzi:
Może się to wydawać sprzeczne z intuicją, ale myślę, że sposobem na zrozumienie tego jest uświadomienie sobie, że pod pewnymi względami odniesienia są traktowane syntaktycznie jak wskaźniki .
Wydaje się to logiczne dla wskaźnika :
int main() { boolalpha(cout); int const i = 1; cout << is_const<decltype(i)>::value << endl; int const* ri = &i; cout << is_const<decltype(ri)>::value << endl; }
Wynik:
true false
Jest to logiczne, ponieważ wiemy, że to nie obiekt wskaźnika jest const (można go wskazać gdzie indziej), ale obiekt, na który jest wskazywany.
Tak więc poprawnie widzimy, że stałość samego wskaźnika została zwrócona jako
false
.Jeśli chcemy zrobić sam wskaźnik ,
const
musimy powiedzieć:int main() { boolalpha(cout); int const i = 1; cout << is_const<decltype(i)>::value << endl; int const* const ri = &i; cout << is_const<decltype(ri)>::value << endl; }
Wynik:
true true
Myślę więc, że widzimy analogię syntaktyczną z odniesieniem .
Jednak odniesienia są semantycznie różni się od wskaźników, zwłaszcza w jednej kluczowej szacunku, nie wolno ponownie powiązać odniesienie do innego obiektu po związaniu.
Więc choć referencje mają taką samą składnię jak wskaźniki zasady są różne, a więc nie pozwala nam język od deklarowania odwołanie sam
const
jak ten:int main() { boolalpha(cout); int const i = 1; cout << is_const<decltype(i)>::value << endl; int const& const ri = i; // COMPILE TIME ERROR! cout << is_const<decltype(ri)>::value << endl; }
Zakładam, że nie możemy tego zrobić, ponieważ wydaje się, że nie jest to potrzebne, gdy reguły języka zapobiegają odbiciu odniesienia w taki sam sposób, jak mógłby to zrobić wskaźnik (jeśli nie jest zadeklarowany
const
).A więc odpowiadając na pytanie:
W twoim przykładzie składnia sprawia, że odniesienie do rzeczy jest
const
takie samo, jak w przypadku deklarowania wskaźnika .Słusznie lub niesłusznie nie wolno nam podawać samego odniesienia ,
const
ale gdybyśmy tak było, wyglądałoby to tak:int const& const ri = i; // not allowed
Dlaczego
decltype()
nie przenosi się tego na obiekt, do którego odwołuje się referencja ?Przypuszczam, że dotyczy to semantycznej równoważności ze wskaźnikami, a być może także funkcją
decltype()
(zadeklarowanego typu) jest spojrzenie wstecz na to, co zostało zadeklarowane przed wiązaniem.źródło
decltype
, i stwierdził, że tak się nie stało.const
, dlategostd::is_const
musi wrócićfalse
. Mogli zamiast tego użyć sformułowania, które oznaczało, że musi wrócićtrue
, ale tak się nie stało . Otóż to! Wszystkie te rzeczy o wskaźnikach, „zakładam”, „przypuszczam” itp. Nie dają żadnego prawdziwego wyjaśnienia.decltype
nie jest operacją w czasie wykonywania, więc idea „w czasie wykonywania, odwołania zachowują się jak wskaźniki”, czy jest poprawna czy nie, tak naprawdę nie ma zastosowania.std::is_const
sprawdza, czy typ ma wartość stałą, czy nie.Ale odwołanie nie może być kwalifikowane jako const. Referencje [dcl.ref] / 1
Więc
is_const<decltype(ri)>::value
zwróci,false
ponieważri
(odwołanie) nie jest typem kwalifikowanym przez stałą. Jak powiedziałeś, nie możemy ponownie powiązać referencji po inicjalizacji, co implikuje, że referencja jest zawsze „stała”, z drugiej strony odwołanie kwalifikowane w postaci stałej lub odwołanie niekwalifikowane w postaci stałej mogą w rzeczywistości nie mieć sensu.źródło
is_const
wracatrue
? Ta odpowiedź próbuje narysować analogię do tego, jak wskaźniki mogą być opcjonalnie zmieniane, podczas gdy odniesienia nie są - i robiąc to, prowadzi do sprzeczności z tym samym powodem. Nie jestem pewien, czy istnieje prawdziwe wyjaśnienie tak czy inaczej, inne niż nieco arbitralna decyzja tych, którzy piszą Standard, i czasami jest to najlepsze, na co możemy mieć nadzieję. Stąd ta odpowiedź.decltype
jest to funkcja i dlatego działa bezpośrednio na samym odnośniku, a nie na obiekcie, do którego się odnosi. (Być może jest to bardziej istotne w przypadku odpowiedzi „odniesienia są w zasadzie wskazówkami”, ale nadal uważam, że jest to część tego, co sprawia, że ten przykład jest zagmatwany i dlatego warto o nim tutaj wspomnieć.)decltype(name)
działa inaczej niż generałdecltype(expr)
. Na przykładdecltype(i)
to zadeklarowany typi
toconst int
, podczas gdydecltype((i))
byłbyint const &
.Musisz użyć,
std::remove_reference
aby uzyskać wartość, której szukasz.std::cout << std::is_const<std::remove_reference<decltype(ri)>::type>::value << std::endl;
Więcej informacji znajdziesz w tym poście .
źródło
Dlaczego nie ma makr
const
? Funkcje? Literały? Nazwy typów?const
rzeczy są tylko podzbiorem niezmiennych rzeczy.Ponieważ typy referencyjne to tylko te - typy - może mieć sens wymaganie
const
na nich wszystkich -kwalifikatorów w celu uzyskania symetrii z innymi typami (szczególnie z typami wskaźnikowymi), ale byłoby to bardzo uciążliwe i bardzo szybko.Gdyby C ++ miał domyślnie niezmienne obiekty, wymagając
mutable
słowa kluczowego na czymkolwiek nie chciałbyś byćconst
, to byłoby to łatwe: po prostu nie pozwól programistom dodawaćmutable
do typów referencyjnych.W obecnej sytuacji są one niezmienne bez zastrzeżeń.
A ponieważ nie są one
const
-kwalifikowane, prawdopodobnie byłoby bardziej zagmatwane , gdybyis_const
typ referencyjny był prawdziwy.Uważam, że jest to rozsądny kompromis, zwłaszcza że niezmienność jest i tak wymuszona przez sam fakt, że nie istnieje składnia, która mogłaby zmienić odniesienie.
źródło
To jest dziwactwo / funkcja w C ++. Chociaż nie myślimy o referencjach jako o typach, w rzeczywistości „siedzą” one w systemie typów. Chociaż wydaje się to niezręczne (biorąc pod uwagę, że gdy używane są odwołania, semantyka odwołań występuje automatycznie, a odniesienie „znika z drogi”), istnieją pewne uzasadnione powody, dla których odniesienia są modelowane w systemie typów zamiast jako oddzielny atrybut poza rodzaj.
Po pierwsze, rozważmy, że nie każdy atrybut zadeklarowanej nazwy musi znajdować się w systemie typów. Z języka C mamy „klasę pamięci” i „powiązanie”. Nazwę można wprowadzić jako
extern const int ri
, gdzieextern
oznacza statyczną klasę pamięci i obecność powiązania. Ten typ jest sprawiedliwyconst int
.C ++ oczywiście obejmuje pogląd, że wyrażenia mają atrybuty, które są poza systemem typów. Język ma teraz koncepcję „klasy wartości”, która jest próbą uporządkowania rosnącej liczby atrybutów innych niż typowe, które może wykazywać wyrażenie.
Jednak odniesienia są typami. Czemu?
Kiedyś wyjaśniono w samouczkach C ++, że deklaracja taka jak
const int &ri
wprowadzonari
jako posiadająca typconst int
, ale semantyka odwołań. Ta semantyka odniesienia nie była typem; był to po prostu rodzaj atrybutu wskazującego na niezwykły związek między nazwą a miejscem przechowywania. Ponadto fakt, że odwołania nie są typami, został wykorzystany do uzasadnienia, dlaczego nie można konstruować typów na podstawie odwołań, mimo że pozwala na to składnia konstrukcji typu. Na przykład tablice lub wskaźniki do odwołań nie są możliwe:const int &ari[5]
iconst int &*pri
.Ale w rzeczywistości odwołania są typami i dlatego
decltype(ri)
pobiera węzeł typu referencyjnego, który jest niekwalifikowany. Musisz zejść poza ten węzeł w drzewie typów, aby dostać się do typu bazowego zremove_reference
.Kiedy używasz
ri
, odniesienie jest rozwiązywane w sposób przezroczysty, więcri
„wygląda i czuje się jaki
” i można je nazwać „aliasem”. Jednak w systemie typówri
w rzeczywistości istnieje typ, który jest „ odniesieniem doconst int
”.Dlaczego istnieją typy referencyjne?
Weź pod uwagę, że gdyby odwołania nie były typami, wówczas te funkcje zostałyby uznane za mające ten sam typ:
void foo(int); void foo(int &);
Po prostu nie może tak być z powodów, które są dość oczywiste. Gdyby miały ten sam typ, oznacza to, że każda deklaracja byłaby odpowiednia dla którejkolwiek z definicji, a zatem każda
(int)
funkcja musiałaby być podejrzewana o przyjmowanie referencji.Podobnie, gdyby referencje nie były typami, te dwie deklaracje klas byłyby równoważne:
class foo { int m; }; class foo { int &m; };
Byłoby poprawne, gdyby jedna jednostka tłumaczeniowa używała jednej deklaracji, a inna jednostka tłumaczeniowa w tym samym programie używała drugiej deklaracji.
Faktem jest, że odniesienie implikuje różnicę w implementacji i nie można go oddzielić od typu, ponieważ typ w C ++ ma do czynienia z implementacją encji: jej „układ” w bitach, że tak powiem. Jeśli dwie funkcje mają ten sam typ, można je wywołać z tymi samymi konwencjami wywoływania binarnego: ABI jest taki sam. Jeśli dwie struktury lub klasy mają ten sam typ, ich układ jest taki sam, jak również semantyka dostępu do wszystkich członków. Obecność odniesień zmienia te aspekty typów, więc włączenie ich do systemu typów jest prostą decyzją projektową. (Należy jednak zwrócić uwagę na kontrargument: może to być element członkowski struktury / klasy
static
, który również zmienia reprezentację; ale to nie jest typ!)Zatem odniesienia znajdują się w systemie typów jako „obywatele drugiej kategorii” (podobnie jak funkcje i tablice w ISO C). Są pewne rzeczy, których nie możemy „zrobić” z odniesieniami, takie jak deklarowanie wskaźników do referencji lub ich tablic. Ale to nie znaczy, że nie są typami. Po prostu nie są typami w sensie, który ma sens.
Nie wszystkie te ograniczenia drugiej klasy są niezbędne. Biorąc pod uwagę, że istnieją struktury odwołań, mogą istnieć tablice odwołań! Na przykład
// fantasy syntax int x = 0, y = 0; int &ar[2] = { x, y }; // ar[0] is now an alias for x: could be useful!
To po prostu nie jest zaimplementowane w C ++, to wszystko. Wskaźniki do odwołań w ogóle nie mają jednak sensu, ponieważ wskaźnik podniesiony z odniesienia po prostu kieruje się do obiektu, do którego się odwołuje. Prawdopodobnym powodem braku tablic odniesień jest to, że ludzie C ++ uważają tablice za rodzaj funkcji niskiego poziomu dziedziczonej po C, która jest zepsuta na wiele sposobów, które są nieodwracalne i nie chcą dotykać tablic jako podstawa do czegoś nowego. Jednak istnienie tablic odniesień stanowiłoby jasny przykład tego, jak odwołania muszą być typami.
Nie-
const
-qualifiable typy: znaleziono w ISO C90, też!Niektóre odpowiedzi sugerują, że odwołania nie wymagają
const
kwalifikatora. Jest to raczej czerwony śledź, ponieważ deklaracjaconst int &ri = i
nawet nie próbuje utworzyćconst
referencji kwalifikowanej: jest to odwołanie do typu z kwalifikacją const (który sam w sobie nie jestconst
). Tak jakconst in *ri
deklaruje wskaźnik do czegośconst
, ale ten wskaźnik sam nie jestconst
.To powiedziawszy, prawdą jest, że referencje nie mogą
const
same nosić kwalifikatora.Jednak nie jest to takie dziwne. Nawet w języku ISO C 90 nie wszystkie typy mogą być
const
. Mianowicie, tablic nie może.Po pierwsze, nie istnieje składnia deklarowania tablicy const:
int a const [42]
jest błędna.Jednak to, co stara się zrobić powyższa deklaracja, można wyrazić za pośrednictwem pośrednika
typedef
:typedef int array_t[42]; const array_t a;
Ale to nie robi tego, na co wygląda. W tej deklaracji nie kwalifikuje się to,
a
co jestconst
kwalifikowane, ale elementy! To znaczy,a[0]
jest aconst int
, alea
jest po prostu „tablicą int”. W konsekwencji nie wymaga to diagnostyki:int *p = a; /* surprise! */
To robi:
a[0] = 1;
Ponownie, to podkreśla ideę, że odwołania są w pewnym sensie „drugą klasą” w systemie typów, podobnie jak tablice.
Zwróć uwagę, że analogia jest jeszcze głębsza, ponieważ tablice mają również „niewidoczne zachowanie konwersji”, takie jak odwołania. Bez konieczności używania przez programistę żadnego jawnego operatora, identyfikator
a
automatycznie zamienia się weint *
wskaźnik, tak jakby&a[0]
zostało użyte wyrażenie . Jest to analogiczne do tego, jak odniesienieri
, gdy używamy go jako wyrażenia pierwotnego, w magiczny sposób oznacza przedmiot,i
z którym jest związane. To tylko kolejny „rozpad”, taki jak „rozpad tablicy na wskaźnik”.I tak jak nie możemy dać się zmylić faktem, że „tablica do wskaźnika” rozpada się na błędne myślenie, że „tablice są tylko wskaźnikami w C i C ++”, tak samo nie powinniśmy myśleć, że odwołania to tylko aliasy, które nie mają własnego typu.
Gdy
decltype(ri)
pomija zwykłą konwersję odwołania do obiektu referencyjnego, nie różni się to tak bardzo odsizeof a
pomijania konwersji tablicy na wskaźnik i operowania na samym typie tablicy w celu obliczenia jej rozmiaru.źródło
decltype
nie wykonuje tej przezroczystej rozdzielczości (nie jest funkcją, więcri
nie jest „używany” w opisywanym przez ciebie sensie). Wpisuje się to bardzo ładnie z całym swoim naciskiem na system typu - ich połączenie kluczem jest to, żedecltype
jest to operacja typu systemu .const X & x ”oznacza x aliasuje obiekt X, ale nie możesz zmienić tego obiektu X za pomocą x.
I zobacz std :: is_const .
źródło