Перейти к содержанию

Последнее обновление:2026-10-12

C++17 optional_ref

Эта статья — пересказ блога foonathan optional_ref discussion; оригинал — лучшее обсуждение того, почему std::optional<T&> (optional-ссылка) так и не попал в стандарт. Тему подняли заново накануне C++26, так что стоит пройтись по ключевым аргументам.

1. Проблема: почему std::optional не компилируется?

std::optional<int>, std::optional<std::string> — без проблем, а std::optional<int&> сразу падает с ошибкой компиляции. Причина довольно «историческая»: при разработке стандарта не договорились о его семантике присваивания, и в стандарт просто записали, что поведение не определено, когда T — ссылочный тип.

А что же такого сложного в «возможно пустой ссылке»? Автор свёл всё к четырём вопросам:

(1) optional<T&> — это то же самое, что указатель?

(2) действительно ли нам нужен optional<T&>?

(3) при присваивании нужно делать rebind (перепривязаться к новому объекту) или assign-through (записать в указуемый объект)?

(4) должен ли у него вообще быть оператор присваивания?

Краткие ответы автора: нет, не нужен, rebind, не должен. Смотрим почему.

2. optional-ссылка — не указатель

Интуитивно «возможно пустая ссылка» — это указатель, но автор указывает на более принципиальное различие:

  • создание и использование T& оба неявные: получил ссылку — сразу пользуйся, разыменовывать не нужно;
  • создание и использование T* оба явные: нужно &x для взятия адреса и *p для разыменования.

Эта явность ценна: указатель напоминает, что «здесь может быть пусто, возможны проблемы с алиасингом». Сделав из optional<T&> «более удобный указатель» с неявными преобразованиями на каждом шагу, мы теряем именно эту защиту. Вывод автора: если нужно выразить «может быть пусто», явный T* — даже лучшая документация.

3. rebind или assign-through

Это главный тупик всего обсуждения:

int a = 1, b = 2;
std::optional<int&> opt = a;
opt = b;  // 问题:这句是什么意思?

Два прочтения:

  • rebind: opt теперь привязан к b (семантика алиаса ссылки; int& ra = a; ra = b; записывает b в a… стоп, присваивание int& — это как раз assign-through!)
  • assign-through: записать значение b в a (семантика обычной ссылки T&).

В этом и противоречие: если optional-ссылка хочет соответствовать семантике T&, присваивание должно быть assign-through; но изменяемый optional (который можно перезаполнить другим объектом) интуитивно требует rebind. Обе семантики разумны, комитет не пришёл к согласию — и optional<T&> так и не легализовали.

На практике, когда нужна семантика rebind, решение сообщества — собственный optional_ref<T>: внутри хранится T*, присваивание — просто смена указателя, семантика предельно ясна. Предложение std::optional_ref для C++26 идёт тем же путём.

4. Итог

(1) optional<T&> — не синтаксический сахар для указателя: у них принципиальное разделение ролей в явности;

(2) его заблокировала двусмысленность семантики присваивания: и rebind, и assign-through звучат логично;

(3) когда нужна «обнуляемая ссылка», явный указатель или optional_ref — более безопасный выбор, чем «легализованный optional».

Напоследок вопрос на подумать: что должны возвращать value() и *opt у optional<T&> — T& или const T&? А если хочется выразить и семантику «захвачено как const», как спроектировать параметр шаблона?

Похожие темы

Комментарии