Saltar a contenido

Última actualización:2026-10-12

C++17 optional_ref

Este artículo reseña el blog de foonathan optional_ref discussion; el original es la mejor discusión sobre por qué std::optional<T&> (una referencia opcional) nunca entró en el estándar. El tema volvió a la mesa justo antes de C++26, así que vale la pena repasar los argumentos centrales.

1. El problema: ¿por qué std::optional no compila?

std::optional<int>, std::optional<std::string> funcionan sin problemas, pero std::optional<int&> da error de compilación directamente. La razón es algo «histórica»: al elaborar el estándar no se llegó a un consenso sobre sus semánticas de asignación, así que sencillamente se escribió en el estándar que el comportamiento es indefinido cuando T es un tipo referencia.

¿Y qué tiene de tan difícil una «referencia que quizá esté vacía»? El autor destila cuatro preguntas:

(1) ¿Es optional<T&> lo mismo que un puntero?

(2) ¿Realmente necesitamos optional<T&>?

(3) Al asignar, ¿debe hacer rebind (revincularse al nuevo objeto) o assign-through (escribir en el objeto apuntado)?

(4) ¿Debería tener operador de asignación en absoluto?

Las respuestas tl;dr del autor: no, no, rebind, y no debería. Veamos por qué.

2. Una referencia opcional no es un puntero

Intuitivamente, una «referencia posiblemente vacía» es un puntero, pero el autor señala una diferencia más esencial:

  • la creación y el acceso de T& son ambos implícitos: con la referencia en mano se usa directamente, sin desreferenciar;
  • la creación y el acceso de T* son ambos explícitos: hace falta &x para tomar la dirección y *p para desreferenciar.

Esta diferencia de explicitud tiene valor: un puntero te recuerda que «aquí podría estar vacío, podría haber problemas de aliasing». Convertir optional<T&> en un «puntero más cómodo» con conversiones implícitas por todas partes tira precisamente esa capa de protección. La conclusión del autor: si quieres expresar «posiblemente vacío», un T* explícito es incluso mejor documentación.

3. ¿rebind o assign-through?

Este es el nudo central de toda la discusión:

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

Dos lecturas:

  • rebind: opt queda ahora vinculado a b (la semántica de alias de las referencias; int& ra = a; ra = b; asigna b a a… espera, ¡la asignación de int& es justamente assign-through!)
  • assign-through: escribir el valor de b en a (la semántica de una referencia normal T&).

Ahí está la contradicción: si la referencia opcional quiere alinearse con la semántica de T&, la asignación debería ser assign-through; pero un optional mutable (que puede volver a contener otro objeto) intuitivamente exige rebind. Ambas semánticas son razonables, el comité no logró ponerse de acuerdo, y así optional<T&> nunca se legalizó.

En la práctica, cuando se quiere semántica de rebind, la solución de la comunidad es fabricar el propio optional_ref<T>: internamente guarda un T*, y asignar es cambiar de puntero, con una semántica clarísima. La propuesta de std::optional_ref de C++26 sigue el mismo camino.

4. Resumen

(1) optional<T&> no es azúcar sintáctico del puntero: ambos tienen una división esencial del trabajo en cuanto a explicitud;

(2) lo que lo bloqueó es la ambigüedad de la semántica de asignación: tanto rebind como assign-through son defendibles;

(3) cuando se necesita una «referencia anulable», un puntero explícito o un optional_ref son opciones más seguras que un «optional legalizado».

Para terminar, una pregunta: ¿deberían value() y *opt de optional<T&> devolver T& o const T&? Y si también se quiere expresar la semántica de «capturado como const», ¿cómo diseñar el parámetro de plantilla?

Temas relacionados

Comentarios