¿Por qué Expected <T> en LLVM implementa dos constructores para Expected <T> &&?

8

Expected<T>se implementa en llvm / Support / Error.h. Es una unión etiquetada que tiene una To una Error.

Expected<T>es una clase de plantilla con tipo T:

template <class T> class LLVM_NODISCARD Expected

Pero estos dos constructores realmente me confunden:

  /// Move construct an Expected<T> value from an Expected<OtherT>, where OtherT
  /// must be convertible to T.
  template <class OtherT>
  Expected(Expected<OtherT> &&Other,
           typename std::enable_if<std::is_convertible<OtherT, T>::value>::type
               * = nullptr) {
    moveConstruct(std::move(Other));
  }

  /// Move construct an Expected<T> value from an Expected<OtherT>, where OtherT
  /// isn't convertible to T.
  template <class OtherT>
  explicit Expected(
      Expected<OtherT> &&Other,
      typename std::enable_if<!std::is_convertible<OtherT, T>::value>::type * =
          nullptr) {
    moveConstruct(std::move(Other));
  }

¿Por qué Expected<T>repite dos construcciones para la misma implementación? ¿Por qué no lo hace así?

template <class OtherT>
Expected(Expected<OtherT>&& Other) { moveConstruct(std::move(Other));}
yodahaji
fuente
1
Tenga en cuenta la explicitpalabra clave
Mat
Quiero saber por qué las explicitpalabras clave son importantes aquí. ¿Alguien podría dar un ejemplo?
yodahaji

Respuestas:

8

Porque ese constructor es condicionalmente explícito de acuerdo con la propuesta. Esto significa que el constructor es explícito solo si se cumple alguna condición (aquí, convertibilidad de Ty OtherT).

C ++ no tiene un mecanismo para esta funcionalidad (algo así como explicit(condition)) antes de C ++ 20. Por lo tanto, las implementaciones deben usar algún otro mecanismo, como la definición de dos constructores diferentes , uno explícito y otro de conversión , y garantizar la selección del constructor adecuado de acuerdo con la condición. Esto normalmente se realiza a través de SFINAE con la ayuda de std::enable_if, donde se resuelve la condición.


Desde C ++ 20, debe haber una versión condicional del explicitespecificador. La implementación sería mucho más fácil con una sola definición:

template <class OtherT>
explicit(!std::is_convertible_v<OtherT, T>)
Expected(Expected<OtherT> &&Other)
{
   moveConstruct(std::move(Other));
}
Daniel Langr
fuente
Gracias por tu respuesta. Aunque todavía estoy confundido, obtuve muchos recursos después de buscar en Google 'condicionalmente explícito'.
yodahaji
@yodahaji Tenga en cuenta que condicionalmente explícito no es un término estándar. Simplemente significa que el constructor es explícito o está convirtiendo de acuerdo con alguna condición.
Daniel Langr
5

Para entender esto debemos comenzar con std::is_convertible. Según cppreference :

Si la definición de la función imaginaria To test() { return std::declval<From>(); }está bien formada (es decir, std::declval<From>()se puede convertir para Tousar conversiones implícitas o ambas Fromy Toposiblemente sean nulas calificadas por cv), proporciona el valor constante del miembro igual a true. De lo contrario, el valor es false. A los fines de esta verificación, el uso de std::declvalen la declaración de devolución no se considera un uso odr.

Las comprobaciones de acceso se realizan como si se tratara de un contexto no relacionado con ninguno de los tipos. Solo se considera la validez del contexto inmediato de la expresión en la declaración de retorno (incluidas las conversiones al tipo de retorno).

La parte importante aquí es que solo busca conversiones implícitas. Por lo tanto, lo que significan las dos implementaciones en su OP es que si OtherTes implícitamente convertible a T, entonces expected<OtherT>es implícitamente convertible a expected<T>. Si OtherTrequiere una conversión explícita a T, entonces Expected<OtherT>requiere y una conversión explícita a Expected<T>.

Aquí hay ejemplos de modelos implícitos y explícitos y sus Expectedhomólogos.

int x;
long int y = x;              // implicit cast ok
Expected<int> ex;
Expected<long int> ey = ex;  // also ok

void* v_ptr;
int* i_ptr = static_cast<int*>(v_ptr);              // explicit cast required
Expected<void*> ev_ptr;
auto ei_ptr = static_cast<Expected<int*>>(ev_ptr);  // also required
patatahooligan
fuente
Gracias por tu respuesta. Pero no puedo entender el significado de 'entonces Expected<OtherT>requiere un reparto explícito para Expected<T>significar'. ¿Qué significa el "reparto explícito" aquí? No puedo imaginar un ejemplo para esto.
yodahaji
Se agregaron algunos ejemplos a la publicación para aclarar qué significan los moldes explícitos. Por lo general, se usan para evitar lanzamientos implícitos accidentales cuando esos podrían introducir errores. Desafortunadamente, no puedo probar el código en este momento, así que si ves un error tipográfico / error, avísame y lo solucionaré.
patatahooligan
Esta afirmación "para evitar lanzamientos implícitos accidentales" responde a mi pregunta. Gracias :)
yodahaji