Почему ссылки не являются «константными» в C ++?

86

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

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?

Троскивы
источник
3
Также boolalpha(cout)очень необычно. Вы могли бы сделать это std::cout << boolalphaвместо этого.
isanae
6
Так много для riтого, чтобы быть неотличимым от псевдонима i.
Kaz
1
Это потому, что ссылка всегда связана с одним и тем же объектом. iтакже является ссылкой, но по историческим причинам вы не объявляете ее как таковую явным образом. Таким образом i, ссылка относится к хранилищу и riявляется ссылкой на то же хранилище. Но нет никакой разницы в природе между i и ri. Поскольку вы не можете изменить привязку ссылки, нет необходимости квалифицировать ее как const. И позвольте мне заявить, что комментарий @Kaz намного лучше проверенного ответа (никогда не объясняйте ссылки с помощью указателей, ref - это имя, ptr - это переменная).
Жан-Батист Юнес,
1
Замечательный вопрос. Прежде чем увидеть этот пример, я ожидал, is_constчто вернусь и trueв этом случае. На мой взгляд, это хороший пример того, почему constв корне все наоборот; "изменяемый" атрибут (а-ля Rust mut) кажется более последовательным.
Кайл Стрэнд
1
Возможно, следует изменить заголовок на "почему is_const<int const &>::valueложь?" или похожие; Я изо всех сил пытаюсь понять какой-либо смысл в этом вопросе, кроме вопроса о поведении черт типа
MM

Ответы:

52

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

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

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).

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

В) Почему «ссылка» не является «константой» в C ++?

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

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

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

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

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

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

Галик
источник
13
Похоже, вы говорите, что «ссылки не могут быть константами, потому что они всегда константы»?
ruakh
2
Это может быть правдой, что « семантически все действия над ними после их привязки передаются объекту, на который они ссылаются», но OP ожидал, что это применимо и к decltype, и обнаружил, что это не так.
ruakh
10
Здесь есть много извилистых предположений и сомнительно применимых аналогий с указателями, которые, я думаю, запутывают проблему больше, чем необходимо, когда проверка стандарта, как sonyuanyao, переходит прямо к реальной точке: ссылка не является cv-квалифицируемым типом, поэтому не может быть const, поэтому std::is_constдолжен вернуться false. Вместо этого они могли бы использовать формулировку, означающую, что он должен вернуться true, но они этого не сделали. Это оно! Вся эта ерунда об указателях, «я полагаю», «я полагаю» и т. Д. Не дает никаких реальных разъяснений.
underscore_d
1
@underscore_d Вероятно, это лучший вопрос для чата, чем для раздела комментариев здесь, но есть ли у вас пример "ненужной путаницы" из-за такого мышления о ссылках? Если они могут быть реализованы таким образом, в чем проблема в том, чтобы думать о них таким образом? (Я не думаю, что этот вопрос имеет значение, потому что decltypeэто не операция времени выполнения, поэтому идея «во время выполнения ссылки ведут себя как указатели», независимо от того, верна она или нет, на самом деле не применима.
Кайл Странд
1
Ссылки также не обрабатываются синтаксически как указатели. У них разный синтаксис для объявления и использования. Так что я даже не понимаю, что вы имеете в виду.
Jordan Melo
55

почему "ri" не "const"?

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

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

Но ссылка не может быть квалифицирована как const. Ссылки [dcl.ref] / 1

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

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

Songyuanyao
источник
5
Прямо к Стандарту и, следовательно, прямо к делу, а не ко всем пустым предположениям принятого ответа.
underscore_d
5
@underscore_d Это хороший ответ, пока вы не поймете, что оператор тоже спрашивал, почему. «Потому что так сказано» не очень полезно для этого аспекта вопроса.
simpleuser
5
@ user9999999 Другой ответ также не отвечает на этот аспект. Если ссылка не может быть восстановлена, почему не is_constвозвращается true? В этом ответе делается попытка провести аналогию с тем, как указатели могут быть повторно заменены, а ссылки - нет - и при этом ведет к внутреннему противоречию по той же причине. Я не уверен, что есть реальное объяснение в любом случае, кроме несколько произвольного решения тех, кто пишет Стандарт, и иногда это лучшее, на что мы можем надеяться. Отсюда и такой ответ.
underscore_d
2
Еще один важный аспект, я думаю, заключается в том, что decltypeэто не функция и поэтому работает непосредственно с самой ссылкой, а не с объектом, на который ссылается. (Возможно, это более актуально для ответа «ссылки - это в основном указатели», но я все же думаю, что это часть того, что сбивает этот пример с толку и поэтому стоит упомянуть здесь.)
Кайл Странд
3
Чтобы прояснить комментарий от @KyleStrand: decltype(name)действует не так, как обычно decltype(expr). Так, например, decltype(i)объявлен тип, iкоторый есть const int, а decltype((i))будет int const &.
Daniel Schepler
18

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

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

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

chema989
источник
8

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

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

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

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

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

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

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

Гонки легкости на орбите
источник
6

Это особенность / особенность 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]это a const int, но aэто просто «массив int». Следовательно, это не требует диагностики:

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

Это делает:

a[0] = 1;

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

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

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

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

Каз
источник
Здесь у вас много интересной и полезной информации (+1), но она более или менее ограничена тем фактом, что «ссылки находятся в системе типов» - это не полностью отвечает на вопрос OP. Между этим ответом и ответом @ songyuanyao я бы сказал, что информации более чем достаточно, чтобы понять ситуацию, но это похоже на необходимый фон для ответа, а не на полный ответ.
Кайл Стрэнд
1
Кроме того, я думаю, что это предложение стоит выделить: «Когда вы используете ri, ссылка прозрачно разрешается ...» Один ключевой момент (который я упомянул в комментариях, но который до сих пор не отображался ни в одном из ответов ) decltype не выполняет это прозрачное разрешение (это не функция, поэтому riне «используется» в том смысле, который вы описываете). Это соответствует очень хорошо всему своему акценту на системе типа - они подключение ключа является то , что decltypeэто операция типа системы .
Кайл Стрэнд,
Хм, материал о массивах кажется касательным, но последнее предложение полезно.
Kyle Strand
... хотя на данный момент я уже проголосовал за вас, и я не ОП, так что вы на самом деле ничего не получаете или не теряете, слушая мою критику ...
Кайл Стрэнд
Очевидно, что простой (и правильный ответ) просто указывает нам на стандарт, но мне нравится исходное мышление здесь, хотя некоторые могут утверждать, что некоторые его части являются предположениями. +1.
Стив Кидд
-5

const X & x »означает, что x является псевдонимом объекта X, но вы не можете изменить этот объект X с помощью x.

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

Sotter.liu
источник
это даже не попытка ответить на вопрос.
bolov