Semantik für umschlossene Objekte: Referenz / Wert standardmäßig über std :: move / std :: ref
In letzter Zeit verwende ich häufig eine natürliche Redewendung, die ich in C ++ 11 "entdeckt" habe, dh, dass umschlossene Objekte automatisch Verweise enthalten können, wenn dies möglich ist. Die Hauptfrage ist hier der Vergleich mit dem Verhalten dieses "Idioms" mit anderen Verhaltensweisen im Standard (siehe unten).
Zum Beispiel:
template<class T>
struct wrap{
T t;
};
template<class T> wrap<T> make_wrap(T&& t){
return wrap{std::forward<T>(t)};
}
Auf diese Weise für den Code
double a = 3.14
double const c = 3.14
Ich bekomme,
typeid( make_wrap(3.14) ) --> wrap<double>
typeid( make_wrap(a) ) --> wrap<double&>
typeid( make_wrap(c) ) --> wrap<double const&>
was, wenn ich vorsichtig bin (mit baumelnden Hinweisen), ich kann ziemlich gut damit umgehen. Und wenn ich willvermeiden Referenzen, die ich mache:
typeid( make_wrap(std::move(a)) ) --> wrap<double> // noref
typeid( make_wrap(std::move(c)) ) --> wrap<double const> // noref
Daher scheint dieses Verhalten in C ++ 11 normal zu sein.
Dann ging ich zurück zustd::pair
undstd::make_pair
und irgendwie erwartete ich, dass sie dieses neue, scheinbar natürliche Verhalten benutzten, aber anscheinend ist das Verhalten "traditioneller". Also zum Beispiel:
typeid( std::make_pair(3.14, 3.14) ) --> std::pair<double, double>
typeid( std::make_pair(a, a) ) --> std::pair<double, double> // noref
typeid( std::make_pair(c, c) ) --> std::pair<double, double> // noref
undzum Verweise:
typeid( std::make_pair(std::ref(a), std::ref(a) ) ) --> std::pair<double&, double&> // ref
typeid( std::make_pair(std::ref(c), std::ref(c) ) ) --> std::pair<double const&, double const&> // const ref
Dies ist hier dokumentiert:http://en.cppreference.com/w/cpp/utility/pair/make_pair
Wie Sie sehen, sind die beiden Verhaltensweisen in gewissem Sinne "gegensätzlich"std::ref
ist die Ergänzung zustd::move
. So sind beide Verhaltensweisen am Ende gleich flexibel, aber es scheint mir, dass diestd::make_pair
Verhalten ist schwieriger zu implementieren und zu pflegen.
Die Frage ist:Ist das aktuelle Verhalten vonstd::make_pair
Referenzen standardmäßig nur ein Abwärtskompatibilitätsproblem zu verwerfen? weil eine historische Erwartung? Oder gibt es einen tieferen Grund, der in C ++ 11 noch existiert?
So sieht es ausstd::make_pair
Verhalten ist viel schwieriger zu implementieren, da es eine Spezialisierung erfordertstd::ref
(std::reference_wrapper
) undstd::decay
und wirkt sogar unnatürlich (in Gegenwart von "C ++ 11 move"). Selbst wenn ich mich entscheide, das erste Verhalten weiterhin zu verwenden, befürchte ich, dass das Verhalten in Bezug auf die aktuellen Standards selbst in C ++ 11 ziemlich unerwartet sein wird.
Tatsächlich mag ich das erste Verhalten ziemlich gern, bis zu dem Punkt, dass die elegante Lösung möglicherweise das Präfix ändertmake_something
für so etwas wieconstruct_something
um den Unterschied im Verhalten zu markieren. (BEARBEITEN: einer der Kommentare schlug vor, zu betrachtenstd::forward_as_tuple
, so könnte eine andere Namenskonvention seinforward_as_something
). In Bezug auf die Benennung ist die Situation nicht eindeutig, wenn bei der Konstruktion des Objekts Pass-by-Value und Pass-by-Ref gemischt werden.
EDIT2: Dies ist eine Bearbeitung, um eine @ Yakk-Frage zu beantworten, ob ein Wrap-Objekt mit verschiedenen ref / value-Eigenschaften "kopiert" werden kann. Dies ist kein Teil der Frage und nur experimenteller Code:
template<class T>
struct wrap{
T t;
// "generalized constructor"? // I could be overlooking something important here
template<class T1 = T> wrap(wrap<T1> const& w) : t(std::move(w.t)){}
wrap(T&& t_) : t(std::move(t)){} // unfortunately I now have to define an explicit constructor
};
Dies scheint mir zu ermöglichen, zwischen nicht verwandten Typen zu kopierenwrap<T&>
undwrap<T>
:
auto mw = make_wrap(a);
wrap<double const&> copy0 =mw;
wrap<double&> copy1 = mw; //would be an error if `a` is `const double`, ok
wrap<double> copy2 = mw;
EDIT3: Diese Änderung soll ein konkretes Beispiel hinzufügen, in dem die herkömmliche Referenzableitung von einem "Protokoll" abhängen kann. Das Beispiel basiert auf der Verwendung von Boost.Fusion.
Ich entdeckte, wie sehr die implizite Umwandlung von Referenz zu Wert von der Konvention abhängen kann. Zum Beispiel folgt die gute alte Boost.Fusion der STL-Konvention von
Die Generierungsfunktionen von Fusion (z. B. make_list) speichern die Elementtypen standardmäßig als reine Nichtreferenztypen.
Dies hängt jedoch von dem genauen "Typ" ab, der die Referenz kennzeichnet, im Fall von Fusion war dasboost::ref
und im Fall vonmake_pair
ist ...std::ref
, eine völlig unabhängige Klasse. Also derzeit gegeben
double a;
die Art vonboost::fusion::make_vector(5., a )
istboost::fusion::vector2<double, double>
. Ok gut
Und die Art vonboost::fusion::make_vector(5., boost::ref(a) ) ) is
boost :: fusion :: vector2`. Ok, wie dokumentiert.
Überraschung, da Boost.Fusion nicht mit C ++ 11 STLs geschrieben wurde, erhalten wir:boost::fusion::make_vector(5., std::ref(a) ) )
ist vom Typboost::fusion::vector2<double, std::reference_wrapper<double const> >
. Überraschung!
Dieser Abschnitt sollte zeigen, dass das aktuelle STL-Verhalten von einem Protokoll abhängt (z. B. welche Klasse zum Kennzeichnen von Referenzen verwendet werden soll), während das andere (das ich "natürliches" Verhalten nannte) verwendetstd::move
(oder genauer gesagt rvalue casting) hängt nicht von einem Protokoll ab, sondern ist in der (aktuellen) Sprache gebürtiger.