Почему GCC отклоняет std:: optional для ссылок?

std::optional<int&> xx; просто не компилируется для последнего моментального снимка gcc-7.0.0. Включает ли стандарт С++ 17 std::optional для ссылок? А почему если нет? (Реализация с указателями в выделенной специализации, я думаю, не вызовет проблем.)


person Vahagn    schedule 02.11.2016    source источник
comment
@Someprogrammerdude optional работает не так. В противном случае у вас не могло бы быть и optional<non_default_constructible>. Вы можете написать необязательный тип, поддерживающий ссылочные типы. Boost делает. Стандарт просто предпочитает этого не делать (из-за operator=).   -  person Barry    schedule 02.11.2016
comment
Практически весь смысл ссылок в том, что они не являются необязательными.   -  person jthill    schedule 03.11.2016
comment
Как насчет std::optional< std::reference_wrapper<int> > :-D   -  person M.M    schedule 03.11.2016
comment
@M.M На самом деле в моем коде оказалось удобнее делать std::optoinal<std::variant<T&>> ։)   -  person Vahagn    schedule 08.11.2016
comment
Возможный дубликат специализации std::Optional для ссылочных типов   -  person jww    schedule 21.09.2018


Ответы (2)


Потому что optional, стандартизированный в C++17, не разрешает ссылочные типы. Это было исключено конструкцией.

Этому есть две причины. Во-первых, структурно говоря, optional<T&> эквивалентно T*. У них могут быть разные интерфейсы, но они делают одно и то же.

Во-вторых, у комитета по стандартам фактически не было единого мнения о том, как именно должен вести себя optional<T&>.

Рассмотрим следующее:

optional<T&> ot = ...;
T t = ...;
ot = t;

Что должна делать эта последняя строка? Берет ли он объект, на который ссылается ot, и копирует-присваивает ему, например, *ot == t? Или он должен перепривязать саму сохраненную ссылку, чтобы ot.get() == &t? Хуже того, будет ли действовать по-разному в зависимости от того, был ли занят ot до назначения?

Кто-то будет ожидать от него одного, а кто-то другого. Так что неважно, какую сторону вы выберете, кто-то будет сбит с толку.

Если бы вместо этого вы использовали T*, было бы совершенно ясно, что происходит:

T* pt = ...;
T t = ...;
pt = t;   //Compile error. Be more specific.
*pt = t;  //Assign to pointed-to object.
pt = &t;  //Change pointer.
person Nicol Bolas    schedule 02.11.2016
comment
Ну, я бы не согласился с первой причиной, так как мне нужен единый интерфейс в универсальном коде. Что касается второй причины, то для меня она точно должна вести себя как *ot = t. Кстати, у boost::optional так и есть? - person Vahagn; 02.11.2016
comment
Так что неважно, какую сторону вы выберете, кто-то будет сбит с толку. -- Значит, решение состоит в том, чтобы запутать обе стороны, верно? ։) - person Vahagn; 02.11.2016
comment
Если ot не занят, я бы даже выбросил исключение, пока ot = t. - person Vahagn; 02.11.2016
comment
@Vahagn: для меня это определенно должно вести себя как *ot = t. Если вы сделаете это, как бы вы перепривязали ссылку? Установите его в NULL, а затем установите его в новую ссылку? И как последний будет работать? Что делать, если у вас есть optional<optional<T&>>? Будет ли выполнение ot = nullopt отключать внешнюю или внутреннюю оболочку? - person Nicol Bolas; 02.11.2016
comment
@NicolBolas, просто сказав o = t, он перепривяжет и *o = t назначит ссылку. У них также есть optional<bool> путаница, и они решили, что работа с необязательным напрямую будет использовать необязательный выбор, а для работы со значением вы должны разыменовать. Ведь если сказать optional<int> o=1; o = nullopt, то он тоже скомпилируется, несмотря на то, что nullopt не конвертируется в int. - person Johannes Schaub - litb; 18.07.2017
comment
@JohannesSchaub-litb: Может быть, это правильный путь. Но даже если это так, факт остается фактом: именно этот вопрос не получил консенсуса в комитете по стандартам C++. Вот почему optional<T&> нет в C++17. - person Nicol Bolas; 18.07.2017
comment
По моему мнению. optional<T&> НЕ эквивалентно T*. Согласно современным представлениям C++, если вы возвращаете T*, вызывающая сторона может подумать, следует ли мне освободить указатель после его использования. Необработанный указатель может запутать, кто является владельцем объекта. Но если вы вернете optional<T&>, вызывающая сторона не будет иметь дело с необязательным параметром, потому что он может быть либо пустым, либо ссылкой. - person Alexander Chen; 02.06.2020
comment
@AlexanderChen: Если мы думаем о современном С++, то T* всегда должно обозначать отсутствие права собственности. Если вы хотите представить передачу права собственности, в современном C++ это можно сделать с помощью какого-либо интеллектуального указателя. Таким образом, единственный раз, когда вызывающая сторона должна думать об этом, это если он имеет дело с кодом, написанным не в современном стиле C++. - person Nicol Bolas; 02.06.2020
comment
Почему бы просто не отключить оператор присваивания для std::Optional? std::Optional должен быть спроектирован таким образом, чтобы его можно было сконструировать и передать в функцию или вернуть из функции. Не для переназначения его значения, что приводит к таким проблемам. - person Nuclear; 29.04.2021
comment
@Nuclear: Почему мы должны отказываться от полезных функций, таких как назначение optional? Для нессылочных типов то, что он делает, имеет смысл и хорошо понятно. Нет причин использовать только optional для связи с/из функции. - person Nicol Bolas; 29.04.2021

В [необязательно]:

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

std::optional<T&> нет. На данный момент вам придется использовать std::optional<std::reference_wrapper<T>>.

person Barry    schedule 02.11.2016
comment
будет ли это поддерживать ->? std::reference_wrapper, похоже, не реализует operator->. Так что o->foo не получится. - person Johannes Schaub - litb; 18.07.2017
comment
@JohannesSchaub-litb Вам понадобятся две косвенные ссылки - например, o->get().foo, потому что -> просто дает вам reference_wrapper. Я очень надеюсь, что в конце концов мы получим optional<T&>, который перепривязывается по заданию. Многие люди убедили меня, что это правильное поведение. - person Barry; 18.07.2017
comment
@JohannesSchaub-litb Я пришел к такому же выводу, поэтому написал класс-оболочку и оказался здесь чтобы узнать, были ли у кого-нибудь подобные идеи. Это преобразует std::optional<T&> в std::optional<std::reference_wrapper<T>>, но автоматически разворачивает ссылку на operator-> и operator*. operator= перепривязывает ссылку. - person monkey0506; 23.08.2019