Почему ссылки запрещены в std :: варианте? - PullRequest
0 голосов
/ 16 января 2019

Я часто пользуюсь boost::variant и довольно хорошо с ним знаком. boost::variant никоим образом не ограничивает ограниченные типы, в частности, они могут быть ссылками:

#include <boost/variant.hpp>
#include <cassert>
int main() {
  int x = 3;
  boost::variant<int&, char&> v(x); // v can hold references
  boost::get<int>(v) = 4; // manipulate x through v
  assert(x == 4);
}

У меня есть реальный вариант использования варианта ссылок для просмотра некоторых других данных.

Затем я был удивлен, обнаружив, что std::variant не допускает ссылки как ограниченные типы, std::variant<int&, char&> не компилируется и говорит здесь явно:

В одном варианте не разрешено хранить ссылки, массивы или тип void.

Интересно, почему это не разрешено? Я не вижу технической причины. Я знаю, что реализации std::variant и boost::variant различны, так что, может быть, это связано с этим? Или авторы считают это небезопасным?

PS: я не могу обойти ограничение std::variant, используя std::reference_wrapper, потому что эталонная оболочка не позволяет присваивать из базового типа.

#include <variant>
#include <cassert>
#include <functional>

int main() {
  using int_ref = std::reference_wrapper<int>;
  int x = 3;
  std::variant<int_ref> v(std::ref(x)); // v can hold references
  static_cast<int&>(std::get<int_ref>(v)) = 4; // manipulate x through v, extra cast needed
  assert(x == 4);
}

1 Ответ

0 голосов
/ 16 января 2019

По сути, причина того, что optional и variant не разрешают ссылочные типы, заключается в том, что существует разногласие относительно того, что назначение (и, в меньшей степени, сравнение) должно делать для таких случаев. optional проще, чем variant, чтобы показать в примерах, поэтому я буду придерживаться этого:

int i = 4, j = 5;
std::optional<int&> o = i;
o = j; // (*)

Отмеченную строку можно интерпретировать как:

  1. Повторите привязку o, такую, что &*o == &j. В результате этой строки сами значения i и j остаются неизменными.
  2. Назначить через o, такое &*o == &i все еще верно, но теперь i == 5.
  3. Полное запрещение назначения.

Назначение - это поведение, которое вы получаете, просто нажимая = до 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 и стать полезной , Ссылки всегда могут быть добавлены позже - или так аргумент пошел.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...