Почему массивы ссылок незаконны?

149

Следующий код не компилируется.

int a = 1, b = 2, c = 3;
int& arr[] = {a,b,c,8};

Что стандарт C ++ говорит об этом?

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

struct cintref
{
    cintref(const int & ref) : ref(ref) {}
    operator const int &() { return ref; }
private:
    const int & ref;
    void operator=(const cintref &);
};

int main() 
{
  int a=1,b=2,c=3;
  //typedef const int &  cintref;
  cintref arr[] = {a,b,c,8};
}

Можно использовать struct cintrefвместо того, const int &чтобы моделировать массив ссылок.

Алексей Малистов
источник
1
Даже если массив был действительным, сохранение необработанного значения '8' в нем не сработало бы. Если вы введете «intlink value = 8;», он ужасно умрет, потому что в значительной степени переведен в «const int & value = 8;». Ссылка должна ссылаться на переменную.
Грант Питерс
3
intlink value = 8;работает. проверь если не веришь.
Алексей Малистов
7
Как указывает Алексей, совершенно правильно связать значение с ссылкой на констант.
avakar
1
Что не работает, так это operator=. Ссылки не могут быть пересмотрены. Если вы действительно хотите , такую семантику - хотя я лично не нашел ситуацию , в которой они практически полезны - тогда std::reference_wrapperбы способ сделать это, так как он на самом деле хранит указатель , но предоставляет ссылки, как operatorс и это позволяет переустановка. Но тогда я бы просто использовал указатель!
underscore_d
1
Оператор = является частным и не реализован, что в C ++ 11 означает = удалить.
Джимми Хартцелл

Ответы:

148

Отвечая на ваш вопрос о стандарте, я могу привести C ++ Standard §8.3.2 / 4 :

Не должно быть ссылок на ссылки, массивов ссылок и указателей на ссылки.

Кирилл В. Лядвинский
источник
32
Что еще сказать?
полиглот
9
должны быть массивы ссылок по той же причине, что у нас есть массивы указателей, та же информация, но разная обработка, нет?
Нее-Ра
6
Если разработчики компиляторов решат, как расширение, разрешить массивы ссылок, то будет ли это успехом? Или есть какие-то реальные проблемы - возможно, неоднозначный код - которые просто сделали бы слишком запутанным определение этого расширения?
Аарон МакДейд
23
@AaronMcDaid Я думаю, что реальная причина - которая так явно отсутствует в этом и подобных обсуждениях - в том, что если бы у кого-то был массив ссылок, как можно было бы различить адрес элемента и адрес его референта? Непосредственное возражение против «Вы не можете поместить ссылку в массив / контейнер / что угодно» состоит в том, что вы можете поместить structединственный элемент, ссылка которого является ссылкой. Но затем, таким образом, теперь у вас есть и ссылка, и родительский объект для имени ... что теперь означает, что вы можете однозначно указать, какой адрес вы хотите. Кажется очевидным ... так почему никто не сказал это?
underscore_d
95
@polyglot What more is there to say?Обоснование почему ? Я все о Стандарте, но просто цитирую его и предполагаю, что это конец дискуссии, похоже на безошибочный способ уничтожить критическую мысль пользователей - наряду с любым потенциалом для развития языка, например, за пределами ограничений пропусков или искусственное ограничение. Слишком много, потому что Стандарт говорит «здесь - и недостаточно», и это очень разумно сказать по следующим практическим причинам ». Я не знаю, почему возражения так охотно идут на ответы, которые говорят только о первом, даже не пытаясь исследовать второе.
underscore_d
64

Ссылки не являются объектами. У них нет собственного хранилища, они просто ссылаются на существующие объекты. По этой причине не имеет смысла иметь массивы ссылок.

Если вы хотите легкий объект который ссылается на другой объект, вы можете использовать указатель. Вы сможете использовать объект structсо ссылочным элементом в качестве объектов в массивах только в том случае, если вы предоставите явную инициализацию для всех ссылочных элементов для всех structэкземпляров. Ссылки не могут быть инициализированы по умолчанию.

Редактировать: Как отмечает jia3ep, в стандартном разделе объявлений есть явный запрет на массивы ссылок.

CB Bailey
источник
6
Ссылки по своей природе такие же, как постоянные указатели, и поэтому они занимают некоторую память (память), чтобы указывать на что-то.
Иназарук
7
Не обязательно. Компилятор может избежать сохранения адреса, если это, например, ссылка на локальный объект.
EFraim
4
Не обязательно. Ссылки в структурах обычно занимают какое-то место. Местные ссылки часто этого не делают. В любом случае, в строгом стандартном смысле ссылки не являются объектами, и (это было для меня новым) именованные ссылки на самом деле не являются переменными .
CB Bailey
26
Да, но это деталь реализации. Объект в модели C ++ является напечатанной областью хранения. Ссылка явно не является объектом, и нет никакой гарантии, что она займет место в каком-либо конкретном контексте.
CB Bailey
9
Проще говоря: вы можете иметь структуру, состоящую всего из 16 ссылок на foo, и использовать ее точно так же, как я хотел бы использовать мой массив ссылок - за исключением того, что вы не можете индексировать 16 ссылок на foo , Это язык бородавок ИМХО.
Грегго
28

Это интересная дискуссия. Ясно, что массивы ссылок совершенно незаконны, но ИМХО причина, почему не так просто сказать «они не объекты» или «они не имеют размера». Я хотел бы отметить, что сами массивы не являются полноценными объектами в C / C ++ - если вы возражаете против этого, попробуйте создать экземпляр некоторых классов шаблонов stl, используя массив в качестве параметра шаблона 'class', и посмотрите, что произойдет. Вы не можете вернуть их, назначить их, передать их в качестве параметров. (параметр массива рассматривается как указатель). Но допустимо создавать массивы из массивов. Ссылки имеют размер, который компилятор может и должен рассчитать - вы не можете использовать sizeof () для ссылки, но вы можете создать структуру, не содержащую ничего, кроме ссылок. Он будет иметь размер, достаточный для размещения всех указателей, которые реализуют ссылки. Ты можешь'

struct mys {
 int & a;
 int & b;
 int & c;
};
...
int ivar1, ivar2, arr[200];
mys my_refs = { ivar1, ivar2, arr[12] };

my_refs.a += 3  ;  // add 3 to ivar1

На самом деле вы можете добавить эту строку в определение структуры

struct mys {
 ...
 int & operator[]( int i ) { return i==0?a : i==1? b : c; }
};

... и теперь у меня есть кое-что, что выглядит много как массив ссылок:

int ivar1, ivar2, arr[200];
mys my_refs = { ivar1, ivar2, arr[12] };

my_refs[1] = my_refs[2]  ;  // copy arr[12] to ivar2
&my_refs[0];               // gives &my_refs.a == &ivar1

Теперь это не настоящий массив, это перегрузка оператора; он не будет делать то, что обычно делают массивы, например, sizeof (arr) / sizeof (arr [0]). Но он делает именно то, что я хочу, чтобы массив ссылок делал с совершенно легальным C ++. За исключением (а) неудобно устанавливать более 3 или 4 элементов, и (б) он выполняет вычисления с использованием нескольких символов?: Что можно сделать с помощью индексации (а не с помощью обычной индексации семантики C-указателя-вычисления-семантики) , но индексация тем не менее). Я хотел бы видеть очень ограниченный тип «массив ссылок», который действительно может сделать это. Т.е. массив ссылок не будет рассматриваться как общий массив вещей, которые являются ссылками, а скорее будет новым «массивом ссылок» сделать с шаблонами).

это, вероятно, сработает, если вы не возражаете против такого рода мерзостей: переделайте '* this' в массив int * и верните ссылку, сделанную из одного: (не рекомендуется, но показывает, как правильно 'array' должно сработать):

 int & operator[]( int i ) { return *(reinterpret_cast<int**>(this)[i]); }
greggo
источник
1
Вы можете сделать это в C ++ 11 std::tuple<int&,int&,int&> abcref = std::tie( a,b,c)- который создает «массив» ссылок, который может быть проиндексирован только константами времени компиляции с использованием std :: get. Но ты не можешь сделать std::array<int&,3> abcrefarr = std::tie( a,b,c). Может быть, есть способ сделать это, о котором я не знаю. Мне кажется, что должен быть способ написать специализацию, std::array<T&,N>которая может сделать это - и, таким образом, функцию, std::tiearray(...)которая возвращает один такой (или позволяет std::array<T&,N>принять инициализатор, содержащий адреса = {& v1, & v2 и т. Д.})
greggo
Ваше предложение глупо (IMO), и ваша последняя строка предполагает, что int может содержать указатель (обычно false, по крайней мере, там, где я работаю). Но это единственный ответ здесь с законным обоснованием того, почему массивы ссылок запрещены, поэтому +1
Nemo
@Nemo это на самом деле не задумывалось как предложение, а просто чтобы проиллюстрировать, что функциональность такой вещи не абсурдна. В своем последнем комментарии я отмечаю, что в c ++ есть вещи, которые очень близки. Кроме того, последняя строка, на которую вы ссылаетесь, предполагает, что память, реализующая ссылку на int, может быть полезна как псевдоним-указатель на int - структура содержит ссылки, а не целые числа - и это имеет значение true. Я не утверждаю, что это портативно (или даже безопасно от правил «неопределенного поведения»). это должно было показать, как индексация может быть использована ?:
скрытно,
Справедливо. Но я думаю, что эта идея "абсурдна на первый взгляд", и именно поэтому массивы ссылок запрещены. Массив - это, прежде всего, контейнер. Все контейнеры нуждаются в типе итератора для применения стандартных алгоритмов. Тип итератора для «массива T» обычно «T *». Каким будет тип итератора для массива ссылок? Количество языковых взломов, которое потребуется для решения всех этих проблем, просто смешно для бесполезной функции. На самом деле, возможно, я сделаю это своим собственным ответом :-)
Nemo
@Nemo Это не обязательно должно быть частью основного языка, так что тип итератора «custom» не составляет особого труда. Все различные типы контейнеров имеют свои типы итераторов. В этом случае итератор, разыменованный, будет ссылаться на целевые объекты, а не на ссылки в массиве, что полностью соответствует поведению массива. Может потребоваться немного нового поведения C ++ для поддержки в качестве шаблона. Очень простой std::array<T,N>, легко определяемый в коде приложения, не был добавлен в std :: до c ++ 11, предположительно потому, что было бы странно иметь это без способа Это = {1,2,3};был «языковой взлом»?
Грегго
28

Комментарий к вашему редактированию:

Лучшее решение есть std::reference_wrapper.

Подробности: http://www.cplusplus.com/reference/functional/reference_wrapper/

Пример:

#include <iostream>
#include <functional>
using namespace std;

int main() {
    int a=1,b=2,c=3,d=4;
    using intlink = std::reference_wrapper<int>;
    intlink arr[] = {a,b,c,d};
    return 0;
}
Youw
источник
Привет, это было заявлено еще в 09. Подсказка ищет новые сообщения :)
Stígandr
9
Пост старый, но не все знают о решении с reference_wrapper. Люди должны прочитать ход о STL и STDlib C ++ 11.
You
Это дает ссылку и пример кода, а не просто упоминание названия класса reference_wrapper.
Дэвид Стоун
12

Массив неявно преобразуется в указатель, а указатель на ссылку недопустим в C ++

Эфраим
источник
11
Это правда, что вы не можете иметь указатель на ссылку, но это не причина, по которой вы не можете иметь массив ссылок. Скорее они оба являются признаками того факта, что ссылки не являются объектами.
Ричард Корден
1
Структура не может содержать ничего, кроме ссылок, и будет иметь размер, пропорциональный их количеству. Если бы у вас был массив ссылок 'arr', обычное преобразование массива в указатель не имело бы смысла, поскольку 'указатель на ссылку' не имеет смысла. но имеет смысл просто использовать arr [i] так же, как st.ref, когда 'st' - это структура, содержащая ref. Хм. Но & arr [0] даст адрес первого объекта, на который ссылаются, а & arr [1] - & arr [0] не будет таким же, как & arr [2] - & arr [1] - это создаст много странностей.
Грегго
11

Учитывая int& arr[] = {a,b,c,8};, что этоsizeof(*arr) ?

Повсюду в другом месте ссылка рассматривается как просто сама вещь, так sizeof(*arr)должно быть sizeof(int). Но это сделало бы арифметику указателя массива в этом массиве неправильной (при условии, что ссылки не совпадают по ширине с точностью до целого). Чтобы устранить двусмысленность, это запрещено.

PaulMurrayCbr
источник
Ага. Вы не можете иметь массив чего-либо, если у него нет размера. Каков размер ссылки?
Дэвид Шварц
4
@DavidSchwartz Сделай structчьим единственным участником является эта ссылка и узнай ..?
underscore_d
3
Это должен быть принятый ответ, а не глупый педантичный, который просто бросает стандарт на ОП.
Тайсон Джейкобс
Может быть, есть опечатка. «предполагая, что ссылки не имеют одинаковую ширину, - это целые дюймы» следует понимать «предполагая, что ссылки - не та же ширина, что и целые». Изменить это в качестве .
Женгуоли
Но подождите, по этой логике вы не можете иметь ссылочные переменные-члены.
Тайсон Джейкобс
10

Потому что, как многие уже говорили здесь, ссылки не являются объектами. они просто псевдонимы. Правда, некоторые компиляторы могут реализовывать их как указатели, но стандарт не заставляет / не указывает это. А поскольку ссылки не являются объектами, вы не можете на них указывать. Хранение элементов в массиве означает, что существует некоторый индексный адрес (т. Е. Указывающий на элементы с определенным индексом); и именно поэтому у вас не может быть массивов ссылок, потому что вы не можете на них указывать.

Используйте вместо него boost :: reference_wrapper или boost :: tuple; или просто указатели.

навигатор
источник
5

Я считаю, что ответ очень прост и связан с семантическими правилами ссылок и с тем, как обрабатываются массивы в C ++.

Вкратце: Ссылки можно рассматривать как структуры, которые не имеют конструктора по умолчанию, поэтому применяются все те же правила.

1) Семантически ссылки не имеют значения по умолчанию. Ссылки могут быть созданы только путем ссылки на что-то. Ссылки не имеют значения для представления отсутствия ссылки.

2) При выделении массива размера X программа создает коллекцию инициализированных по умолчанию объектов. Поскольку ссылка не имеет значения по умолчанию, создание такого массива семантически недопустимо.

Это правило также применяется к структурам / классам, которые не имеют конструктора по умолчанию. Следующий пример кода не компилируется:

struct Object
{
    Object(int value) { }
};

Object objects[1]; // Error: no appropriate default constructor available
Криступас А.
источник
1
Вы можете создать массив Object, вы просто должны убедиться , чтобы инициализировать их всех: Object objects[1] = {Object(42)};. Между тем, вы не можете создать массив ссылок, даже если вы инициализируете их все.
Антон3
4

Вы можете довольно близко подобраться к этой структуре шаблона. Однако вам нужно инициализировать с помощью выражений, которые указывают на T, а не на T; и поэтому, хотя вы можете легко сделать «fake_constref_array» аналогичным образом, вы не сможете привязать это к значениям, как это сделано в примере OP ('8');

#include <stdio.h>

template<class T, int N> 
struct fake_ref_array {
   T * ptrs[N];
  T & operator [] ( int i ){ return *ptrs[i]; }
};

int A,B,X[3];

void func( int j, int k)
{
  fake_ref_array<int,3> refarr = { &A, &B, &X[1] };
  refarr[j] = k;  // :-) 
   // You could probably make the following work using an overload of + that returns
   // a proxy that overloads *. Still not a real array though, so it would just be
   // stunt programming at that point.
   // *(refarr + j) = k  
}

int
main()
{
    func(1,7);  //B = 7
    func(2,8);     // X[1] = 8
    printf("A=%d B=%d X = {%d,%d,%d}\n", A,B,X[0],X[1],X[2]);
        return 0;
}

-> A = 0 B = 7 X = {0,8,0}

greggo
источник
2

Ссылочный объект не имеет размера. Если вы напишите sizeof(referenceVariable), он даст вам размер объекта, на который ссылается referenceVariable, а не размер самой ссылки. Он не имеет собственного размера, поэтому компилятор не может рассчитать, какой размер потребуется массиву.

Карл Селеборг
источник
3
если так, как тогда компилятор может вычислить размер структуры, содержащей только ссылку?
Juster
Компилятор знает физический размер ссылки - он такой же, как указатель. Ссылки на самом деле являются семантически прославленными указателями. Разница между указателями и ссылками заключается в том, что ссылки содержат довольно много семантических правил, чтобы уменьшить количество ошибок, которые вы можете создать по сравнению с указателями.
Криступас А.
2

Когда вы сохраняете что-либо в массиве, его размер должен быть известен (поскольку индексирование массива зависит от размера). В соответствии со стандартом C ++ Не указано, требуется ли ссылка для хранения, в результате индексирование массива ссылок будет невозможно.

Pradyot
источник
1
Но, поместив указанную ссылку в struct, волшебным образом все становится определенным, четко определенным, гарантированным и имеющим детерминированный размер. Почему же существует эта, казалось бы, полностью искусственная разница?
underscore_d
... конечно, если кто-то действительно начинает думать о том, что structдает обертка , начинают предлагать себя некоторые хорошие возможные ответы - но мне, никто еще этого не сделал, несмотря на то, что это является одной из непосредственных проблем для всех общих обоснование того, почему вы не можете использовать развернутые ссылки.
underscore_d
2

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

Амит Кумар
источник
Рассмотрим приведенный ниже код из исходного вопроса: int a = 1, b = 2, c = 3; int & arr [] = {a, b, c, 8}; Существует очень малая вероятность того, что a, b, c будут находиться в последовательной ячейке памяти.
Амит Кумар
Это далеко от основной проблемы с концепцией хранения ссылок в контейнере.
underscore_d
0

Рассмотрим массив указателей. Указатель - это действительно адрес; поэтому, когда вы инициализируете массив, вы аналогично говорите компьютеру: «выделите этот блок памяти для хранения этих чисел X (которые являются адресами других элементов)». Затем, если вы меняете один из указателей, вы просто меняете то, на что он указывает; это все еще числовой адрес, который сам находится в том же месте.

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

Дэн Тао
источник
1
Почему же тогда создание structединственного члена, являющегося эталоном, приводит к чему-то совершенно законному, предсказуемому и неаморфному? Почему пользователь должен обернуть ссылку, чтобы он мог волшебным образом получить способности с поддержкой массивов, или это просто искусственное ограничение? ИМО без ответа напрямую обратился к этому. Я предполагаю, что опровержение: «Объект обертки гарантирует, что вы можете использовать семантику значений в форме объекта обертывания, поэтому он гарантирует, что вы можете иметь гарантии значений, которые необходимы, например, как, например, можно устранить неоднозначность между элементом и адресом рефери» ... Это то, что вы имели в виду?
underscore_d
-2

На самом деле, это смесь синтаксиса C и C ++.

Вы должны либо использовать чистые массивы C, которые не могут быть ссылками, поскольку ссылки являются частью только C ++. Или вы идете по пути C ++ и используете std::vectorилиstd::array класс для своих целей.

Что касается редактируемой части: несмотря на то, что structэто элемент из C, вы определяете конструктор и операторные функции, которые делают его C ++ class. Следовательно, ваш structне будет компилироваться в чистом C!

Thargon
источник
В чем ваша точка зрения? std::vectorили std::arrayне может содержать ссылки либо. Стандарт C ++ совершенно ясен, что ни один контейнер не может.
underscore_d