Чому посилання не є “const” у C ++?


86

Ми знаємо, що "змінна 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

Дивно. Ми знаємо, що як тільки посилання прив’язане до імені / змінної, ми не можемо змінити це прив’язку, ми змінюємо його пов’язаний об’єкт. Тому я припускаю, що тип riповинен бути таким самим, як i: коли iє int const, чому riні const?


3
Крім того, boolalpha(cout)дуже незвично. Ви можете std::cout << boolalphaзамість цього зробити .
isanae

6
Так багато для riтого, щоб бути "псевдонімом", який неможливо відрізнити i.
Каз

1
Це тому, що посилання завжди обмежене одним і тим же об’єктом. iтакож є посиланням, але з історичних причин ви не заявляєте його як такий явним чином. Таким чином i, посилання, яке відноситься до сховища, riє посиланням, яке відноситься до того самого сховища. Але різниці в природі між i і ri. Оскільки ви не можете змінити прив'язку посилання, немає необхідності кваліфікувати його як const. І дозвольте мені стверджувати, що коментар @Kaz набагато кращий за перевірену відповідь (ніколи не пояснюйте посилання за допомогою покажчиків, ref - це назва, ptr - змінна).
Жан-Батіст Юнес

1
Фантастичне запитання. До того, як побачити цей приклад, я також очікував is_constби повернутися trueв цьому випадку. На мій погляд, це хороший приклад того, чому constпринципово назад; "змінний" атрибут (а-ля Руст mut), здається, був би більш послідовним.
Kyle Strand

1
Можливо, слід змінити заголовок на "чому is_const<int const &>::valueнеправда?" або подібні; Я намагаюся побачити будь-яке значення цього питання, крім запитання про поведінку типових рис
М. М.

Відповіді:


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

Це логічно, оскільки ми знаємо, що це не об’єкт-вказівник, який є 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).

Отже, щоб відповісти на питання:

З) Чому “посилання” не є “const” у C ++?

У вашому прикладі синтаксис робить посилання на річ constтаким же, як і у випадку, якщо ви оголошували вказівник .

Справедливо чи неправильно нам не дозволяється робити посилання на себе, constале якби це було, це виглядало б так:

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

З) ми знаємо, як тільки посилання прив'язується до імені / змінної, ми не можемо змінити це прив'язку, ми змінюємо його прив'язаний об'єкт. Тому я припускаю, що тип riповинен бути таким самим, як i: коли iє int const, чому riні const?

Чому decltype()не передається об'єкту, до якого прив'язується суддя ?

Я припускаю, що це для семантичної еквівалентності з покажчиками, і, можливо, функція decltype()(оголошеного типу) полягає в тому, щоб оглянути те, що було оголошено до прив'язки.


13
Схоже, ви кажете: "посилання не можуть бути const, тому що вони завжди const"?
ruahh

2
Може бути правдою, що " семантично всі дії на них після того, як вони пов'язані, переносяться на об'єкт, на який вони посилаються", але ОП очікував, що це також стосуватиметься decltype, і виявив, що це не так.
ruahh

10
Тут є багато звивистих припущень та сумнівно застосовних аналогій до покажчиків, що, на мою думку, плутає проблему більше, ніж потрібно, коли перевірка стандарту, як це робив сонюаняо, переходить прямо до справжньої суті: посилання не є типом, що підлягає CV, тому цього не може бути const, тому std::is_constповинен повернутися false. Вони могли замість цього використати формулювання, яке означало, що воно має повернутися true, але вони цього не зробили. Це воно! Всі ці речі про вказівники, "я припускаю", "я припускаю" тощо не дають жодного реального з'ясування.
underscore_d

1
@underscore_d Це, мабуть, краще запитання для чату, ніж для розділу коментарів тут, але чи є у вас приклад "непотрібної плутанини" від такого мислення про посилання? Якщо їх можна реалізувати таким чином, у чому проблема думати про них так? (Я не думаю, що це питання враховується, оскільки decltypeце не операційна програма, тому ідея "під час виконання посилання поводяться як вказівники", правильна чи ні, насправді не застосовується.
Кайл Стренд,

1
Посилання також не трактуються синтаксично, як покажчики. Вони мають різний синтаксис для оголошення та використання. Тож я навіть не впевнений, що ти маєш на увазі.
Джордан Мело

55

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

std::is_const перевіряє, чи тип відповідає 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(посилання) не є типом, що відповідає вимогам const. Як ви вже сказали, ми не можемо повторно прив'язати посилання після ініціалізації, що означає, що посилання завжди є "const", з іншого боку, посилання, яке відповідає const, або посилання, яке не відповідає умовам, може насправді не мати сенсу.


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 &.
Даніель Шеплер

18

Вам потрібно використовувати, std::remove_referenceщоб отримати цінність, яку ви шукаєте.

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

Для отримання додаткової інформації дивіться цю публікацію .


7

Чому макроси не є const? Функції? Літерали? Назви типів?

const речі - це лише підмножина незмінних речей.

Оскільки посилальні типи - це саме такі типи - можливо, було б певним сенсом вимагати constвід них -кваліфікатора на всіх них для симетрії з іншими типами (зокрема, з типами покажчиків), але це може дуже втомлювати дуже швидко.

Якби в C ++ за замовчуванням були незмінні об'єкти, що вимагають mutableключового слова для будь-чого, чим ви не хотіли б бути const, тоді це було б легко: просто не дозволяйте програмістам додаватиmutable до посилальних типів.

В даний час вони незмінні без кваліфікації.

І оскільки вони не є const-кваліфікованими, можливо, було б більш заплутаним для is_constпосилального типу дати true.

Я вважаю, що це розумний компроміс, тим більше, що незмінність у будь-якому випадку забезпечується лише тим фактом, що не існує синтаксису для мутації посилання.


5

Це примха / функція в C ++. Хоча ми не думаємо про посилання як про типи, вони насправді "сидять" у системі типів. Незважаючи на те, що це здається незручним (враховуючи, що при використанні посилань семантика посилань відбувається автоматично, і посилання "зникає з дороги"), є кілька захищених причин, чому посилання моделюються в системі типів, а не як окремий атрибут поза типу.

По-перше, давайте врахуємо, що не кожен атрибут оголошеного імені повинен бути в системі типів. З мови С ми маємо "клас зберігання" та "зв'язок". Ім’я може бути введено як extern const int ri, де externвказує статичний клас зберігання та наявність зв’язку. Тип справедливийconst int .

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

Проте посилання - це типи. Чому?

Раніше в підручниках з 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]є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, я б сказав, що інформації для розуміння ситуації є більш ніж достатньо, але вона здається необхідним передумовою для відповіді, а не повною відповіддю.
Kyle Strand

1
Крім того, я думаю, що це речення варто виділити: "Коли ви використовуєте ri, посилання прозоро вирішується ..." Один ключовий момент (який я вже згадував у коментарях, але який до цього часу не відображався в жодній відповіді ) - це те, decltype що не виконує цю прозору роздільну здатність (це не функція, тому riне використовується в тому сенсі, який ви описуєте). Це дуже добре поєднується з усім вашим акцентом на типовій системі - ключовим зв’язком є ​​те, що decltypeє операцією типової системи .
Кайл Стренд,

Хм, речі про масиви здаються дотичними, але останнє речення корисне.
Kyle Strand

..... хоча на даний момент я вже висловився за вас, і я не є ОП, тож ви насправді нічого не отримуєте і не втрачаєте, слухаючи мою критику ....
Кайл Странд

Очевидно, що легка (і правильна відповідь) просто вказує нам на стандарт, але мені подобається фонове мислення тут, хоча деякі можуть стверджувати, що його частини є припущеннями. +1.
Стів Кідд,

-5

const X & x ”означає, що х псевдонім об’єкта X, але ви не можете змінити цей об’єкт X через x.

І див. Std :: is_const .


це навіть не намагається відповісти на питання.
bolov
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.