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) ) ) isboost :: 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.

Antworten auf die Frage(0)

Ihre Antwort auf die Frage