Почему ссылки не "const" в С++?

Мы знаем, что "константная переменная" указывает, что после назначения вы не можете изменить переменную, например:

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

Weird. Мы знаем, что как только ссылка привязана к имени/переменной, мы не можем изменить это связывание, мы меняем связанный объект. Поэтому я предполагаю, что тип ri должен быть таким же, как i: когда i является int const, почему ri not const?

Ответ 1

Это может показаться противоречивым, но я думаю, что способ понять это - понять, что в некоторых отношениях ссылки обрабатываются синтаксически, как указатели.

Это кажется логичным для указателя:

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

Это логично, потому что мы знаем, что объект-указатель не является const (его можно указать в другом месте), на который указывает объект.

Итак, мы правильно видим, что константа самого указателя возвращается как 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).

Итак, чтобы ответить на вопрос:

Q) Почему "ссылка" не является "константой" в С++?

В вашем примере синтаксис делает ссылку на const так же, как если бы вы объявляли указатель.

Правильно или неправильно нам не разрешается делать ссылку const, но если бы это было так:

int const& const ri = i; // not allowed

Q), мы знаем, что когда ссылка привязана к имени/переменной, мы не можем изменить эту привязку, мы меняем связанный объект. Поэтому я предполагаю, что тип ri должен быть таким же, как i: когда i является int const, почему ri не const?

Почему decltype() не передается объекту, к которому привязан рефери?

Я предполагаю, что это для семантической эквивалентности с указателями, и, возможно, также функция decltype() (объявленный тип) заключается в том, чтобы оглянуться назад на то, что было объявлено до того, как была проведена привязка.

Ответ 2

почему "ри" не "конст"?

std::is_const проверяет, является ли тип квалифицированным или нет.

Если T является квалифицированным по const типом (то есть const или const volatile), значение константы члена равно true. Для любого другого типа значение равно false.

Но ссылка не может быть постоянной. Ссылки [dcl.ref]/1

Cv-квалифицированные ссылки плохо сформированы, за исключением случаев, когда cv-квалификаторы вводятся посредством использования typedef-name ([dcl.typedef], [temp.param]) или спецификатора decltype ([dcl.type.simple]), в этом случае cv-квалификаторы игнорируются.

Поэтому is_const<decltype(ri)>::value вернет false потому что ri (ссылка) не является константным типом. Как вы сказали, мы не можем перепривязать ссылку после инициализации, что подразумевает, что ссылка всегда "const", с другой стороны, ссылка с квалификацией const или ссылка без констант не могут иметь смысла на самом деле.

Ответ 3

Вам нужно использовать std::remove_reference для получения значения, которое вы ищете.

std::cout << std::is_const<std::remove_reference<decltype(ri)>::type>::value << std::endl;

Для получения дополнительной информации см. этот пост.

Ответ 4

Почему макросы не const? Функции? Литералы? Имена типов?

const все это лишь подмножество неизменных вещей.

Так как ссылочные типы - это только — типы — возможно, имело смысл требовать от const -qualifier для всех симметрии с другими типами (особенно с типами указателей), но это очень утомительно очень быстро.

Если у С++ были неизменяемые объекты по умолчанию, требуя ключевое слово mutable на всех, что вы не хотели быть const, тогда это было бы просто: просто не позволяйте программистам добавлять mutable к ссылке типы.

Как бы то ни было, они неизменны без квалификации.

И, поскольку они не являются const -qualified, скорее всего, будет более запутанным для is_const для ссылочного типа для получения истины.

Я считаю, что это разумный компромисс, тем более, что неизменность в любом случае обеспечивается тем фактом, что не существует синтаксиса для изменения ссылки.

Ответ 5

Это quirk/feature в С++. Хотя мы не рассматриваем ссылки как типы, они фактически "сидят" в системе типов. Хотя это кажется неудобным (учитывая, что, когда ссылки используются, эталонная семантика происходит автоматически и ссылка "убирается с пути" ), есть некоторые обоснованные причины, по которым ссылки моделируются в системе типов, а не как отдельный атрибут вне тип.

Во-первых, давайте рассмотрим, что не каждый атрибут объявленного имени должен находиться в системе типов. С языка C мы имеем "класс хранения" и "связь". Имя может быть введено как extern const int ri, где extern указывает статический класс хранения и наличие связи. Тип просто const int.

С++, очевидно, охватывает понятие, что выражения имеют атрибуты, которые находятся за пределами системы типов. Язык теперь имеет понятие "класс ценности", которое является попыткой упорядочить растущее число атрибутов не-типа, которые может выражать выражение.

Но ссылки - это типы. Почему?

В учебниках на С++ объяснялось, что объявление типа const int &ri ввело ri как имеющее тип const int, но ссылочную семантику. Эта ссылочная семантика не была типом; это просто своего рода атрибут, указывающий на необычную связь между именем и местом хранения. Более того, тот факт, что ссылки не являются типами, использовался для того, чтобы рационализировать, почему вы не можете создавать типы на основе ссылок, даже если это позволяет синтаксис построения типа. Например, массивы или указатели на ссылки не возможны: const int &ari[5] и const int &*pri.

Но на самом деле ссылки являются типами, поэтому decltype(ri) извлекает некоторый ссылочный тип node, который является неквалифицированным. Вы должны спуститься через это node в дереве типов, чтобы перейти к базовому типу с помощью remove_reference.

Когда вы используете ri, ссылка прозрачно разрешена, так что ri "выглядит и выглядит как i" и может называться для него псевдонимом. В системе типов, однако, ri действительно имеет тип, который является " ссылкой на const int".

Почему типы ссылок?

Учтите, что если ссылки были не, то эти функции считались бы одинаковыми:

void foo(int);
void foo(int &);

Это просто не может быть по причинам, которые в значительной степени очевидны. Если они имеют один и тот же тип, это означает, что любая декларация подходит для любого определения, и поэтому каждая функция (int) должна быть заподозрена в получении ссылки.

Аналогично, если ссылки не были типами, то эти два объявления класса были бы эквивалентны:

class foo {
  int m;
};

class foo {
  int &m;
};

Было бы правильно, чтобы одна единица перевода использовала одно объявление и другую единицу перевода в той же программе, чтобы использовать другое объявление.

Дело в том, что ссылка подразумевает разницу в реализации, и невозможно отделить ее от типа, потому что тип в С++ связан с реализацией объекта: его "макет" в бит, так сказать. Если две функции имеют один и тот же тип, они могут быть вызваны с теми же двоичными вызовами: ABI - это то же самое. Если две структуры или классы имеют один и тот же тип, их макет такой же, как и семантика доступа ко всем членам. Наличие ссылок изменяет эти аспекты типов, и поэтому прямое дизайнерское решение включает их в систему типов. (Однако обратите внимание на контраргумент здесь: элемент struct/class может быть 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, которая по-разному нарушается, что непоправимо, и они не хотят касаться массивов, поскольку основы для чего-то нового. Существование массивов ссылок, тем не менее, станет ярким примером того, как должны быть типы ссылок.

Non- const -qualifiable types: также найдены в ISO C90!

Некоторые ответы намекают на то, что ссылки не принимают квалификатор const. Это скорее красная селедка, потому что объявление const int &ri = i даже не пытается сделать const -qualified reference: это ссылка на тип, определенный константой (который сам по себе не является 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] является const int, но a является просто "массивом int". Следовательно, это не требует диагностики:

int *p = a; /* surprise! */

Это делает:

a[0] = 1;

Опять же, это подчеркивает мысль о том, что ссылки в некотором смысле являются "вторым классом" в системе типов, например массивами.

Обратите внимание, что аналогия выполняется еще глубже, так как массивы также имеют "невидимое поведение преобразования", например, ссылки. Если программист не должен использовать какой-либо явный оператор, идентификатор a автоматически превращается в указатель int *, как если бы использовалось выражение &a[0]. Это аналогично тому, как ссылка ri, когда мы используем ее как первичное выражение, магически обозначает объект i, к которому он привязан. Это просто еще один "распад", такой как "массив для разложения указателя".

И так же, как мы не должны путаться в результате разложения "массива на указатель", ошибочно полагая, что "массивы - это просто указатели на C и С++", мы также не должны думать, что ссылки - это просто псевдонимы, которые не имеют типа их собственных.

Когда decltype(ri) подавляет обычное преобразование ссылки на его объект-референт, это не так сильно отличается от sizeof a, подавляющего преобразование от массива к указателю, и работает по самому типу массива для вычисления его размера.

Ответ 6

const X & x "означает x aliases объект X, но вы не можете изменить этот объект X через x.

И посмотрите std:: is_const.