Последнее обновление: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¶
Это главный тупик всего обсуждения:
Два прочтения:
- 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», как спроектировать параметр шаблона?
Похожие темы¶
- Атрибут [[nodiscard]] в C++17 | C++那些事
- std::string_view в C++17 | C++那些事
- PMR-ресурсы памяти в C++17 | C++那些事
- Семнадцать расширенных возможностей C++17 | C++那些事
- Выведение аргументов шаблона класса CTAD в C++17 | C++那些事
- std::variant в C++17 | C++那些事
- Структурированные привязки в C++17 | C++那些事
- Выведение auto в шаблонах C++17 | C++那些事
- Шаблоны переменных в C++17 | C++那些事