Ответ 1
По сути, причина, по которой optional
и variant
не допускают ссылочные типы, состоит в том, что существует разногласие в отношении того, что присваивание (и, в меньшей степени, сравнение) должно делать для таких случаев. optional
проще, чем variant
чтобы показать в примерах, поэтому я буду придерживаться этого:
int i = 4, j = 5;
std::optional<int&> o = i;
o = j; // (*)
Отмеченная строка может быть интерпретирована как:
- Повторите
o
, чтобы&*o == &j
. В результате этой строки значенияi
иj
сами остаются неизменными. - Назначьте через
o
, такое&*o == &i
по-прежнему верно, но теперьi == 5
. - Запретить назначение полностью.
Assign-through - это поведение, которое вы получаете, просто нажимая =
до T
=
, rebind - более надежная реализация и именно то, что вам действительно нужно (см. Также этот вопрос, а также доклад Мэтта Калабрезе о ссылочных типах).
Другой способ объяснить разницу между (1) и (2) состоит в том, как мы могли бы реализовать оба варианта внешне:
// rebind
o.emplace(j);
// assign through
if (o) {
*o = j;
} else {
o.emplace(j);
}
Дополнительная документация Boost.Optional обеспечивает следующее обоснование:
Семантика переплета для назначения инициализированных необязательных ссылок была выбрана для обеспечения согласованности между состояниями инициализации даже за счет отсутствия согласованности с семантикой голых ссылок C++. Это правда, что
optional<U>
стремится вести себя как можно лучше, чем U, когда он инициализируется; но в случае, когдаU
являетсяT&
, это может привести к непоследовательному поведению относительно состояния инициализации lvalue.Представьте
optional<T&>
переадресацияoptional<T&>
для объекта, на который есть ссылка (таким образом, изменяете значение объекта, на который указывает ссылка, но не привязывает), и рассмотрите следующий код:optional<int&> a = get(); int x = 1 ; int& rx = x ; optional<int&> b(rx); a = b ;
Что делает назначение?
Если
a
неинициализирован, ответ ясен: он связывается сx
(теперь у нас есть другая ссылка наx
). Но что, если a уже инициализирован? это изменило бы значение ссылочного объекта (что бы это ни было); что не согласуется с другим возможным случаем.Если бы
optional<T&>
присваивал точно так же, как иT&
, вы никогда не сможете использовать необязательное присваивание без явной обработки предыдущего состояния инициализации, если ваш код не способен функционировать независимо от того,a
ли после присваивания псевдоним того же объекта, что иb
или нет.То есть вам нужно было бы различать, чтобы быть последовательным.
Если в вашем коде привязка к другому объекту невозможна, то весьма вероятно, что привязка в первый раз также не подходит. В таком случае присвоение неинициализированного
optional<T&>
должно быть запрещено. Вполне возможно, что в таком сценарии является предварительным условием, что значение lvalue должно быть уже инициализировано. Если это не так, тогда связывание в первый раз в порядке, а повторное связывание - нет, что очень маловероятно. В таком случае вы можете назначить само значение непосредственно, как в:assert(!!opt); *opt=value;
Отсутствие согласия в отношении того, что должна делать эта строка, означало, что было проще просто полностью запретить ссылки, так что большая часть значения optional
и variant
может, по крайней мере, сделать это для C++ 17 и стать полезной. Ссылки всегда могут быть добавлены позже - или так аргумент пошел.