Universal References in C++11

von Hubert Schmid vom 2012-10-14

Ich habe mir kürzlich eine Aufzeichnung eines Vortrags über Universal References in C++11 von Scott Meyers angeschaut. Ich schätze ihn sehr für seine ausgezeichneten Bücher und empfehlenswerten Schulungen. Bei diesem Thema finde ich jedoch, dass er die Sache wesentlich komplizierter macht, als sie tatsächlich ist.

Der Ausdruck type& steht in C++ für einen L‑Value-Referenztyp, wobei type sowohl ein Typname als auch ein Typausdruck sein kann. Mit C++11 wurden sowohl R‑Value-Referenztypen als auch die Syntax type&& eingeführt. Der Vortrag beschäftigt sich fast die gesamten 75 Minuten mit der Frage, ob type&& einen R‑Value-Referenztyp repräsentiert.

Dabei lässt es sich aus meiner Sicht sehr einfach in zwei Sätzen zusammenfassen:

  1. type& ist ein L‑Value-Referenztyp.
  2. type&& ist nur dann ein L‑Value-Referenztyp, wenn type bereits ein L‑Value-Referenztyp ist, in allen anderen Fällen ist type&& ein R‑Value-Referenztyp.

Scott Meyers hat den Begriff Universal Reference geprägt. Eigentlich bezieht er sich nicht auf Typen sondern auf Templates, die sowohl für L‑Value- als auch für R‑Value-Referenztypen instanziiert werden können. Das folgende Beispiel enthält so einen Fall:

template <typename Exception> auto make_exception_ptr(Exception&& e) -> std::exception_ptr { try { throw std::forward<Exception>(e); } catch (...) { return std::current_exception(); } }

Die Frage ist, ob es sich bei Exception&& in diesem Beispiel um einen L‑Value- oder einen R‑Value-Referenztyp handelt? Die Antwort ist so einfach wie sie nur sein kann: Exception ist kein Typ sondern nur ein Typparameter. Daher hängt der Typ von Exception&& von der Instanziierung ab. Im folgenden Listing sind drei Fälle mit expliziter Typangabe dargestellt.

auto&& problem = []() { return std::runtime_error{"Okay, Houston, we've had a problem here."}; }; // Exception=std::runtime_error is not an l-value reference type, // -> implies Exception&& is an r-value reference type. make_exception_ptr<std::runtime_error>(problem()); // Exception=std::runtime_error& is an l-value reference type, // -> implies Exception&& is an l-value reference type. auto&& e = problem(); make_exception_ptr<std::runtime_error&>(e); // Exception=std::runtime_error&& is an r-value reference type, // -> implies Exception&& is an r-value reference type. make_exception_ptr<std::runtime_error&&>(problem());

Durch die explizite Typangabe sollte in all diesen Fällen klar sein, wann Exception&& ein L‑Value- und wann ein R‑Value-Referenztyp ist. Wird der Typ hingegen automatisch bestimmt, ist ein wenig Erfahrung sicherlich hilfreich. Das folgende Listing zeigt die beiden relevanten Fälle:

// Exception is std::runtime_error& and Exception&& is an l-value reference type. std::runtime_error problem{"Okay, Houston, we've had a problem here."}; make_exception_ptr(problem); // Exception is std::runtime_error and Exception&& is an r-value reference type. make_exception_ptr(std::runtime_error{"Okay, Houston, we've had a problem here."});

Es gibt also universelle Template-Funktionen, deren Parametertypen sowohl L‑Value- als auch R‑Value-Referenztypen sein können. Es gibt aber eigentlich keine Universal References. Und eigentlich sind die Regeln für die Bedeutung von && auch sehr einfach.