Мы знаем, что «константная переменная» указывает, что после назначения вы не можете изменить переменную, например:
int const i = 1;
i = 2;
Приведенная выше программа не скомпилируется; gcc выводит сообщение об ошибке:
assignment of read-only variable 'i'
Нет проблем, я могу это понять, но следующий пример выходит за рамки моего понимания:
#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;
}
Он выводит
true
false
Странно. Мы знаем, что как только ссылка привязана к имени / переменной, мы не можем изменить эту привязку, мы меняем связанный с ней объект. Итак, я полагаю, что тип ri
должен быть таким же, как i
: когда i
есть int const
, а почему ri
нет const
?
boolalpha(cout)
очень необычно. Вы могли бы сделать этоstd::cout << boolalpha
вместо этого.ri
того, чтобы быть неотличимым от псевдонимаi
.i
также является ссылкой, но по историческим причинам вы не объявляете ее как таковую явным образом. Таким образомi
, ссылка относится к хранилищу иri
является ссылкой на то же хранилище. Но нет никакой разницы в природе междуi
иri
. Поскольку вы не можете изменить привязку ссылки, нет необходимости квалифицировать ее какconst
. И позвольте мне заявить, что комментарий @Kaz намного лучше проверенного ответа (никогда не объясняйте ссылки с помощью указателей, ref - это имя, ptr - это переменная).is_const
что вернусь иtrue
в этом случае. На мой взгляд, это хороший пример того, почемуconst
в корне все наоборот; "изменяемый" атрибут (а-ля Rustmut
) кажется более последовательным.is_const<int const &>::value
ложь?" или похожие; Я изо всех сил пытаюсь понять какой-либо смысл в этом вопросе, кроме вопроса о поведении черт типаОтветы:
Это может показаться нелогичным, но я думаю, что способ понять это - понять, что в некоторых отношениях ссылки синтаксически обрабатываются как указатели .
Для указателя это кажется логичным :
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; }
Выход:
true false
Это логично, потому что мы знаем, что константой является не объект-указатель (его можно заставить указывать в другом месте), а объект, на который указывает.
Таким образом, мы правильно видим постоянство самого указателя , возвращаемого как
false
.Если мы хотим создать сам указатель ,
const
мы должны сказать: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; }
Выход:
true true
Итак, я думаю, мы видим синтаксическую аналогию со ссылкой .
Однако ссылки семантически отличаются от указателей, особенно в одном важном отношении: нам не разрешается повторно связывать ссылку на другой объект после привязки.
Таким образом, хотя ссылки имеют тот же синтаксис, что и указатели, правила разные, поэтому язык не позволяет нам объявить саму ссылку
const
следующим образом: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; }
Я предполагаю, что нам не разрешено это делать, потому что в этом нет необходимости, когда правила языка предотвращают повторное связывание ссылки так же, как это может сделать указатель (если он не объявлен
const
).Итак, чтобы ответить на вопрос:
В вашем примере синтаксис делает
const
ссылку на объект так же, как если бы вы объявляли указатель .Правильно или ошибочно, нам не разрешено делать саму ссылку ,
const
но в противном случае это выглядело бы так:int const& const ri = i; // not allowed
Почему
decltype()
не переносится объект, к которому привязана ссылка ?Я полагаю, это для семантической эквивалентности с указателями, и, возможно, также функция
decltype()
(объявленного типа) - оглядываться на то, что было объявлено до того, как произошло связывание.источник
decltype
, и обнаружил, что это не так.const
, поэтомуstd::is_const
должен вернутьсяfalse
. Вместо этого они могли бы использовать формулировку, означающую, что он должен вернутьсяtrue
, но они этого не сделали. Это оно! Вся эта ерунда об указателях, «я полагаю», «я полагаю» и т. Д. Не дает никаких реальных разъяснений.decltype
это не операция времени выполнения, поэтому идея «во время выполнения ссылки ведут себя как указатели», независимо от того, верна она или нет, на самом деле не применима.std::is_const
проверяет, является ли тип квалифицированным или нет.Но ссылка не может быть квалифицирована как const. Ссылки [dcl.ref] / 1
Так
is_const<decltype(ri)>::value
будет возвращено,false
потому чтоri
(ссылка) не является константным типом. Как вы сказали, мы не можем повторно привязать ссылку после инициализации, что подразумевает, что ссылка всегда является «константой», с другой стороны, ссылка с указанием константы или неквалифицированная ссылка с константой на самом деле может не иметь смысла.источник
is_const
возвращаетсяtrue
? В этом ответе делается попытка провести аналогию с тем, как указатели могут быть повторно заменены, а ссылки - нет - и при этом ведет к внутреннему противоречию по той же причине. Я не уверен, что есть реальное объяснение в любом случае, кроме несколько произвольного решения тех, кто пишет Стандарт, и иногда это лучшее, на что мы можем надеяться. Отсюда и такой ответ.decltype
это не функция и поэтому работает непосредственно с самой ссылкой, а не с объектом, на который ссылается. (Возможно, это более актуально для ответа «ссылки - это в основном указатели», но я все же думаю, что это часть того, что сбивает этот пример с толку и поэтому стоит упомянуть здесь.)decltype(name)
действует не так, как обычноdecltype(expr)
. Так, например,decltype(i)
объявлен тип,i
который естьconst int
, аdecltype((i))
будетint const &
.Вам нужно использовать,
std::remove_reference
чтобы получить то, что вы ищете.std::cout << std::is_const<std::remove_reference<decltype(ri)>::type>::value << std::endl;
Для получения дополнительной информации см. Этот пост .
источник
Почему макросы нет
const
? Функции? Литералы? Названия типов?const
вещи - это только подмножество неизменных вещей.Поскольку ссылочные типы - это всего лишь типы - возможно, имело смысл потребовать для
const
них всех -qualifier для симметрии с другими типами (особенно с типами указателей), но это очень быстро станет очень утомительным.Если бы в C ++ по умолчанию были неизменяемые объекты, требующие
mutable
ключевого слова для всего, чем вы не хотели бытьconst
, то это было бы легко: просто не позволяйте программистам добавлятьmutable
к ссылочным типам.Как бы то ни было, они неизменны без оговорок.
И, поскольку они
const
неквалифицированы, вероятно, было бы более запутанным дляis_const
ссылочного типа дать true.Я считаю это разумным компромиссом, тем более что неизменность в любом случае обеспечивается простым фактом отсутствия синтаксиса для изменения ссылки.
источник
Это особенность / особенность C ++. Хотя мы не думаем о ссылках как о типах, на самом деле они «сидят» в системе типов. Хотя это кажется неудобным (учитывая, что при использовании ссылок семантика ссылок возникает автоматически, а ссылка «уходит с дороги»), есть несколько оправданных причин, по которым ссылки моделируются в системе типов, а не как отдельный атрибут вне тип.
Во-первых, давайте учтем, что не каждый атрибут объявленного имени должен быть в системе типов. Из языка C у нас есть «класс хранения» и «связь». Имя может быть представлено как
extern const int ri
, гдеextern
указывает статический класс хранения и наличие связи. Типа простоconst int
.C ++, очевидно, поддерживает идею о том, что выражения имеют атрибуты, не входящие в систему типов. В языке теперь есть концепция «класса значений», которая представляет собой попытку организовать растущее число нетиповых атрибутов, которые может отображать выражение.
Но ссылки - это типы. Зачем?
Раньше в учебных пособиях по C ++ объяснялось, что объявление вроде
const int &ri
представленоri
как имеющее типconst int
, но ссылающееся на семантику. Эта ссылочная семантика не была типом; это был просто своего рода атрибут, указывающий на необычную связь между именем и местом хранения. Более того, тот факт, что ссылки не являются типами, был использован для объяснения того, почему вы не можете создавать типы на основе ссылок, даже если синтаксис построения типов это позволяет. Например, массивы или указатели на ссылки невозможны:const int &ari[5]
иconst int &*pri
.Но на самом деле ссылки являются типами, и поэтому
decltype(ri)
извлекается некоторый неквалифицированный узел ссылочного типа. Вы должны пройти мимо этого узла в дереве типов, чтобы добраться до базового типа с помощьюremove_reference
.Когда вы используете
ri
, ссылка становится прозрачной, так чтоri
"выглядит и ощущается какi
» и может называться для нее «псевдонимом». Однако в системе типовri
действительно есть тип, являющийся « ссылкой наconst int
».Почему ссылочные типы?
Учтите, что если бы ссылки были не типами, то считалось бы, что эти функции имеют один и тот же тип:
void foo(int); void foo(int &);
Этого просто не может быть по причинам, которые в значительной степени очевидны. Если бы у них был один и тот же тип, это означает, что любое объявление подходило бы для любого определения, и поэтому
(int)
нужно было бы подозревать , что каждая функция принимает ссылку.Точно так же, если бы ссылки не были типами, эти два объявления классов были бы эквивалентны:
class foo { int m; }; class foo { int &m; };
Было бы правильно, если бы одна единица перевода использовала одно объявление, а другая единица трансляции в той же программе использовала другое объявление.
Дело в том, что ссылка подразумевает различие в реализации, и ее невозможно отделить от типа, потому что тип в C ++ имеет отношение к реализации сущности: ее «макет» в битах, так сказать. Если две функции имеют один и тот же тип, они могут быть вызваны с одинаковыми соглашениями о двоичных вызовах: ABI одинаков. Если две структуры или классы имеют один и тот же тип, их макет будет таким же, как и семантика доступа ко всем членам. Присутствие ссылок меняет эти аспекты типов, поэтому их включение в систему типов - несложное дизайнерское решение. (Однако обратите внимание на контраргумент: член структуры / класса может быть
static
, что также меняет представление; но это не тип!)Таким образом, ссылки находятся в системе типов как «граждане второго сорта» (аналогично функциям и массивам в ISO C). Есть определенные вещи, которые мы не можем «делать» со ссылками, например, объявлять указатели на ссылки или их массивы. Но это не значит, что они не типажи. Они просто не являются типами в том смысле, в котором это имеет смысл.
Не все эти второсортные ограничения существенны. Учитывая, что существуют структуры ссылок, могут быть массивы ссылок! Например
// fantasy syntax int x = 0, y = 0; int &ar[2] = { x, y }; // ar[0] is now an alias for x: could be useful!
Это просто не реализовано в C ++, вот и все. Указатели на ссылки вообще не имеют смысла, потому что указатель, извлеченный из ссылки, просто переходит к объекту, на который указывает ссылка. Вероятная причина, по которой нет массивов ссылок, заключается в том, что люди, работающие с C ++, считают массивы своего рода низкоуровневой функцией, унаследованной от C, которая нарушена многими непоправимыми способами, и они не хотят касаться массивов как основа для ничего нового. Однако существование массивов ссылок явилось бы наглядным примером того, как ссылки должны быть типами.
Не-
const
-qualifiable типов: найдены в ISO C90, тоже!Некоторые ответы намекают на то, что ссылки не требуют
const
квалификатора. Это скорее отвлекающий маневр, потому что объявлениеconst int &ri = i
даже не пытается сделатьconst
-квалифицированную ссылку: это ссылка на константный тип (который сам по себе не являетсяconst
). Точно так же, какconst in *ri
объявляет указатель на что-тоconst
, но сам указатель не являетсяconst
.Тем не менее, действительно, ссылки не могут нести
const
квалификатор сами по себе.Однако это не так уж и странно. Даже на языке ISO C 90 не все типы могут быть
const
. А именно массивов быть не может.Во-первых, не существует синтаксиса для объявления массива const:
int a const [42]
он ошибочен.Однако то, что пытается сделать приведенное выше объявление, можно выразить через промежуточное звено
typedef
:typedef int array_t[42]; const array_t a;
Но это не так, как кажется. В этом объявлении квалифицируется не то,
a
чтоconst
квалифицируется, а элементы! Другими словами,a[0]
это aconst int
, ноa
это просто «массив int». Следовательно, это не требует диагностики:int *p = a; /* surprise! */
Это делает:
a[0] = 1;
Опять же, это подчеркивает идею о том, что ссылки в некотором смысле являются «вторым классом» в системе типов, как массивы.
Обратите внимание, что аналогия сохраняется еще глубже, поскольку массивы также имеют «невидимое поведение преобразования», как и ссылки. Если программисту не нужно использовать какой-либо явный оператор, идентификатор
a
автоматически превращается вint *
указатель, как если бы выражение&a[0]
было использовано. Это аналогично тому, как ссылкаri
, когда мы используем ее в качестве основного выражения, волшебным образом обозначает объект.i
к которому она привязана. Это просто очередной «распад» вроде «распада массива на указатель».И точно так же, как мы не должны сбиваться с толку из-за того, что «массив в указатель» превращается в ошибочное мнение, что «массивы - это просто указатели в C и C ++», мы также не должны думать, что ссылки - это просто псевдонимы, не имеющие собственного типа.
Когда
decltype(ri)
подавляется обычное преобразование ссылки на референтный объект, это не сильно отличается отsizeof a
подавления преобразования массива в указатель и работы с самим типом массива для вычисления его размера.источник
decltype
не выполняет это прозрачное разрешение (это не функция, поэтомуri
не «используется» в том смысле, который вы описываете). Это соответствует очень хорошо всему своему акценту на системе типа - они подключение ключа является то , чтоdecltype
это операция типа системы .const X & x »означает, что x является псевдонимом объекта X, но вы не можете изменить этот объект X с помощью x.
И посмотрите std :: is_const .
источник