Warum implementiert Expected <T> in LLVM zwei Konstruktoren für Expected <T> &&?

8

Expected<T>ist in llvm / Support / Error.h implementiert. Es ist eine getaggte Vereinigung, die entweder ein Toder ein hält Error.

Expected<T>ist eine Vorlagenklasse mit dem Typ T:

template <class T> class LLVM_NODISCARD Expected

Aber diese beiden Konstruktoren verwirren mich wirklich:

  /// 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));
  }

Warum werden Expected<T>zwei Konstrukte für dieselbe Implementierung wiederholt? Warum macht es das nicht so?:

template <class OtherT>
Expected(Expected<OtherT>&& Other) { moveConstruct(std::move(Other));}
Yodahaji
quelle
1
Beachten Sie das explicitSchlüsselwort
Mat
Ich möchte wissen, warum explicitKeywords hier wichtig sind. Könnte jemand ein Beispiel geben?
Jodahaji

Antworten:

8

Weil dieser Konstruktor gemäß dem Vorschlag bedingt explizit ist . Dies bedeutet, dass der Konstruktor nur dann explizit ist, wenn eine Bedingung erfüllt ist (hier Konvertierbarkeit von Tund OtherT).

C ++ hat explicit(condition)vor C ++ 20 keinen Mechanismus für diese Funktionalität (so etwas wie ). Implementierungen müssen daher einen anderen Mechanismus verwenden, beispielsweise eine Definition von zwei verschiedenen Konstruktoren - einen expliziten und einen anderen konvertierenden - und die Auswahl des richtigen Konstruktors gemäß der Bedingung sicherstellen. Dies erfolgt normalerweise über SFINAE mit Hilfe von std::enable_if, wo der Zustand behoben wird.


Seit C ++ 20 sollte es eine bedingte Version des explicitBezeichners geben. Die Implementierung wäre dann mit einer einzigen Definition viel einfacher:

template <class OtherT>
explicit(!std::is_convertible_v<OtherT, T>)
Expected(Expected<OtherT> &&Other)
{
   moveConstruct(std::move(Other));
}
Daniel Langr
quelle
Danke für deine Antwort. Obwohl ich immer noch verwirrt bin, habe ich viele Ressourcen erhalten, nachdem ich "bedingt explizit" gegoogelt habe.
Jodahaji
@yodahaji Beachten Sie, dass bedingt explizit kein Standardbegriff ist. Es bedeutet einfach, dass der Konstruktor explizit ist oder gemäß einer bestimmten Bedingung konvertiert .
Daniel Langr
5

Um dies zu verstehen, sollten wir damit beginnen std::is_convertible. Laut cppreference :

Wenn die imaginäre Funktionsdefinition To test() { return std::declval<From>(); }wohlgeformt ist (dh entweder std::declval<From>()in Toimplizite Konvertierungen konvertiert werden kann oder beides Fromund Tomöglicherweise eine CV-qualifizierte Leere ist), wird der Elementkonstantenwert gleich bereitgestellt true. Ansonsten ist Wert false. Für die Zwecke dieser Prüfung wird die Verwendung vonstd::declval in der return-Anweisung nicht als odr-Verwendung angesehen.

Zugriffsprüfungen werden wie aus einem Kontext durchgeführt, der mit keinem der beiden Typen in Beziehung steht. Es wird nur die Gültigkeit des unmittelbaren Kontexts des Ausdrucks in der return-Anweisung (einschließlich Konvertierungen in den return-Typ) berücksichtigt.

Der wichtige Teil hierbei ist, dass nur nach impliziten Konvertierungen gesucht wird. Daher bedeuten die beiden Implementierungen in Ihrem OP, dass wenn OtherTimplizit konvertierbar in T, dann expected<OtherT>implizit in konvertierbar ist expected<T>. Wenn OtherTerfordert eine explizite Umwandlung zu T, dann Expected<OtherT>erfordert und explizite Umwandlung zuExpected<T> .

Hier sind Beispiele für implizite und explizite Besetzungen und ihre ExpectedGegenstücke

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
quelle
Danke für deine Antwort. Aber ich kann die Bedeutung für "dann Expected<OtherT>erfordert eine explizite Besetzung, um zu Expected<T>bedeuten" nicht verstehen. Was bedeutet die "explizite Besetzung" hier? Ich kann mir kein Beispiel dafür vorstellen.
Jodahaji
Dem Beitrag wurden einige Beispiele hinzugefügt, um zu verdeutlichen, was explizite Besetzungen bedeuten. Im Allgemeinen werden sie verwendet, um versehentliche implizite Würfe zu verhindern, wenn diese möglicherweise Fehler verursachen würden. Leider kann ich den Code momentan nicht testen. Wenn Sie also einen Tippfehler / Fehler entdecken, lassen Sie es mich bitte wissen und ich werde ihn beheben.
Patatahooligan
Diese Aussage "um versehentliche implizite Abdrücke zu verhindern" beantwortet meine Frage. Vielen Dank :)
Yodahaji