Это особенность / особенность 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). Есть определенные вещи, которые мы не можем «делать» со ссылками, например, объявлять указатели на ссылки или их массивы. Но это не значит, что они не типажи. Они просто не являются типами в том смысле, в котором это имеет смысл.
Не все эти второсортные ограничения существенны. Учитывая, что существуют структуры ссылок, могут быть массивы ссылок! Например
int x = 0, y = 0;
int &ar[2] = { x, y };
Это просто не реализовано в 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]
это a const int
, но a
это просто «массив int». Следовательно, это не требует диагностики:
int *p = a;
Это делает:
a[0] = 1;
Опять же, это подчеркивает идею о том, что ссылки в некотором смысле являются «вторым классом» в системе типов, как массивы.
Обратите внимание, что аналогия сохраняется еще глубже, поскольку массивы также имеют «невидимое поведение преобразования», как и ссылки. Если программисту не нужно использовать какой-либо явный оператор, идентификатор a
автоматически превращается в int *
указатель, как если бы выражение &a[0]
было использовано. Это аналогично тому, как ссылка ri
, когда мы используем ее в качестве основного выражения, волшебным образом обозначает объект.i
к которому она привязана. Это просто очередной «распад» вроде «распада массива на указатель».
И точно так же, как мы не должны сбиваться с толку из-за того, что «массив в указатель» превращается в ошибочное мнение, что «массивы - это просто указатели в C и C ++», мы также не должны думать, что ссылки - это просто псевдонимы, не имеющие собственного типа.
Когда decltype(ri)
подавляется обычное преобразование ссылки на референтный объект, это не сильно отличается от sizeof a
подавления преобразования массива в указатель и работы с самим типом массива для вычисления его размера.
boolalpha(cout)
очень необычно. Вы могли бы сделать этоstd::cout << boolalpha
вместо этого.