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