Darf ich Elemente aus einem verschieben std::initializer_list<T>
?
#include <initializer_list>
#include <utility>
template<typename T>
void foo(std::initializer_list<T> list)
{
for (auto it = list.begin(); it != list.end(); ++it)
{
bar(std::move(*it)); // kosher?
}
}
Schon seit std::intializer_list<T>
besondere Aufmerksamkeit des Compilers erfordert und keine Wertesemantik wie bei normalen Containern der C ++ - Standardbibliothek aufweist, möchte ich lieber auf Nummer sicher gehen und fragen.
c++
templates
c++11
move-semantics
initializer-list
Fredoverflow
quelle
quelle
initializer_list<T>
sind nicht -const. Wieinitializer_list<int>
bezieht sich aufint
Objekte. Aber ich denke, das ist ein Defekt - es ist beabsichtigt, dass Compiler eine Liste statisch im Nur-Lese-Speicher zuordnen können.Antworten:
Nein, das wird nicht wie beabsichtigt funktionieren. Sie erhalten weiterhin Kopien. Ich bin ziemlich überrascht darüber, da ich gedacht hatte, dass
initializer_list
es eine Reihe von Provisorien gibt, bis siemove
fertig sind.begin
undend
für dieinitializer_list
Rückgabeconst T *
ist das Ergebnismove
in Ihrem CodeT const &&
- eine unveränderliche Wertreferenz. Ein solcher Ausdruck kann nicht sinnvoll verschoben werden. Es wird an einen Funktionsparameter vom TypT const &
gebunden, da rWerte an konstante Wertreferenzen gebunden sind und Sie weiterhin die Kopiersemantik sehen.Wahrscheinlich liegt der Grund dafür darin, dass der Compiler das wählen kann
initializer_list
statisch initialisierte Konstante festlegen kann, aber es scheint sauberer zu sein, den Typinitializer_list
oderconst initializer_list
nach Ermessen des Compilers festzulegen, sodass der Benutzer nicht weiß, ob er eineconst
oder eine veränderbare Konstante erwarten soll Ergebnis vonbegin
undend
. Aber das ist nur mein Bauchgefühl, wahrscheinlich gibt es einen guten Grund, warum ich falsch liege.Update: Ich habe einen ISO-Vorschlag zur
initializer_list
Unterstützung von Nur-Verschieben-Typen geschrieben. Es ist nur ein erster Entwurf, und er ist noch nirgendwo implementiert, aber Sie können ihn zur weiteren Analyse des Problems sehen.quelle
std::move
sicher, wenn nicht produktiv ist. (Außer ZugkonstrukteureT const&&
.)const std::initializer_list<T>
oder nurstd::initializer_list<T>
auf eine Weise vorbringen könnten, die nicht oft Überraschungen hervorruft. Bedenken Sie, dass jedes Argument in deminitializer_list
entwederconst
oder nicht sein kann und das im Kontext des Aufrufers bekannt ist, aber der Compiler muss nur eine Version des Codes im Kontext des Angerufenen generieren (dhfoo
darin weiß er nichts über die Argumente dass der Anrufer vorbeikommt)std::initializer_list &&
Überladung etwas bewirkt , auch wenn auch eine Überlastung ohne Referenz erforderlich ist. Ich nehme an, es wäre noch verwirrender als die derzeitige Situation, die bereits schlecht ist.Nicht so, wie Sie es beabsichtigen. Sie können ein
const
Objekt nicht verschieben . Undstd::initializer_list
bietet nurconst
Zugriff auf seine Elemente. So die Art derit
heißtconst T *
.Ihr Anrufversuch
std::move(*it)
führt nur zu einem l-Wert. IE: eine Kopie.std::initializer_list
verweist auf statischen Speicher. Dafür ist die Klasse da. Sie können nicht bewegen aus statischen Speicher, da die Bewegung es ändert sich bringt. Sie können nur davon kopieren.quelle
initializer_list
verweist auf den Stack, falls dies erforderlich ist. (Wenn der Inhalt nicht konstant ist, ist es immer noch threadsicher.)initializer_list
Objekt selbst kann ein x-Wert sein, aber sein Inhalt (das tatsächliche Array von Werten, auf das es zeigt) ist esconst
, da diese Inhalte statische Werte sein können. Sie können sich einfach nicht vom Inhalt eines entferneninitializer_list
.const
x-Wert erzeugt.move
mag bedeutungslos sein, aber es ist legal und sogar möglich, einen Parameter zu deklarieren, der genau das akzeptiert. Wenn das Verschieben eines bestimmten Typs ein No-Op ist, funktioniert es möglicherweise sogar richtig.std::move
. Dies stellt sicher, dass Sie anhand der Inspektion erkennen können, wann ein Verschiebungsvorgang ausgeführt wird, da dies sowohl die Quelle als auch das Ziel betrifft (Sie möchten nicht, dass dies implizit für benannte Objekte erfolgt). Aus diesem Grund ist der Code irreführend , wenn Sie ihnstd::move
an einem Ort verwenden, an dem keine Verschiebungsoperation ausgeführt wird (und bei einemconst
x-Wert keine tatsächliche Bewegung stattfindet). Ich denke, es ist ein Fehlerstd::move
, auf einemconst
Objekt aufrufbar zu sein .const &&
einer in Müll gesammelten Klasse mit verwalteten Zeigern, in der alles Relevante veränderlich war und das Verschieben die Zeigerverwaltung bewegte, aber den enthaltenen Wert nicht beeinflusste. Es gibt immer schwierige Randfälle: v).Dies funktioniert nicht wie angegeben, da
list.begin()
Typ hatconst T *
ist und Sie sich nicht von einem konstanten Objekt entfernen können. Die Sprachdesigner haben dies wahrscheinlich gemacht, damit Initialisierungslisten beispielsweise Zeichenfolgenkonstanten enthalten können, von denen es unangemessen wäre, sie zu verschieben.Wenn Sie sich jedoch in einer Situation befinden, in der Sie wissen, dass die Initialisierungsliste rWert-Ausdrücke enthält (oder Sie den Benutzer zwingen möchten, diese zu schreiben), gibt es einen Trick, mit dem dies funktioniert (ich wurde von der Antwort von Sumant für inspiriert dies, aber die Lösung ist viel einfacher als diese). Die in der Initialisiererliste gespeicherten Elemente müssen keine
T
Werte sein, sondern Werte, die kapselnT&&
. Selbst wenn diese Werte selbstconst
qualifiziert sind, können sie dennoch einen veränderbaren Wert abrufen.Anstatt ein
initializer_list<T>
Argument zu deklarieren, deklarieren Sie jetzt eininitializer_list<rref_capture<T> >
Argument. Hier ist ein konkretes Beispiel mit einem Vektor vonstd::unique_ptr<int>
intelligenten Zeigern, für die nur die Bewegungssemantik definiert ist (daher können diese Objekte selbst niemals in einer Initialisierungsliste gespeichert werden). Die unten stehende Initialisierungsliste lässt sich jedoch problemlos kompilieren.Eine Frage muss beantwortet werden: Wenn die Elemente der Initialisierungsliste echte Werte sein sollen (im Beispiel sind es x-Werte), stellt die Sprache dann sicher, dass sich die Lebensdauer der entsprechenden Provisorien bis zu dem Punkt erstreckt, an dem sie verwendet werden? Ehrlich gesagt glaube ich nicht, dass der relevante Abschnitt 8.5 des Standards dieses Problem überhaupt behandelt. Wenn man jedoch 1,9: 10 liest, scheint es, dass der relevante vollständige Ausdruck in allen Fällen die Verwendung der Initialisiererliste umfasst, so dass ich denke, dass keine Gefahr besteht, dass rvalue-Referenzen baumeln.
quelle
"Hello world"
? Wenn Sie sich von ihnen entfernen, kopieren Sie einfach einen Zeiger (oder binden eine Referenz).{..}
sind an Referenzen im Funktionsparameter von gebundenrref_capture
. Dies verlängert nicht ihre Lebensdauer, sie werden am Ende des vollständigen Ausdrucks, in dem sie erstellt wurden, immer noch zerstört.std::initializer_list<rref_capture<T>>
in ein Transformationsmerkmal Ihrer Wahl ein,std::decay_t
um beispielsweise unerwünschte Abzüge zu blockieren.Ich dachte, es könnte lehrreich sein, einen vernünftigen Ausgangspunkt für eine Problemumgehung anzubieten.
Kommentare inline.
quelle
initializer_list
verschoben werden kann und nicht, ob jemand Problemumgehungen hatte. Außerdem ist das Hauptverkaufsargument von,initializer_list
dass es nur für den Elementtyp und nicht für die Anzahl der Elemente verwendet wird und daher keine Empfänger erforderlich sind - und dies verliert dies vollständig.initializer_list
aber nicht allen Einschränkungen unterliegen, die es nützlich machen. :)initializer_list
(über Compiler Magic) vermieden wird, dass Funktionen für die Anzahl der Elemente vorlagen müssen, was von Alternativen, die auf Arrays und / oder variadischen Funktionen basieren, von Natur aus erforderlich ist, wodurch der Bereich der Fälle eingeschränkt wird, in denen letztere verwendbar sind. Nach meinem Verständnis ist dies genau eine der wichtigsten Gründe dafürinitializer_list
, weshalb es erwähnenswert schien.Es scheint im aktuellen Standard nicht erlaubt zu sein, wie bereits beantwortet . Hier ist eine weitere Problemumgehung, um etwas Ähnliches zu erreichen, indem Sie die Funktion als variadisch definieren, anstatt eine Initialisierungsliste zu erstellen.
Variadic-Vorlagen können im Gegensatz zu initializer_list R-Wert-Referenzen angemessen verarbeiten.
In diesem Beispielcode habe ich eine Reihe kleiner Hilfsfunktionen verwendet, um die variadischen Argumente in einen Vektor zu konvertieren und sie dem ursprünglichen Code ähnlich zu machen. Natürlich können Sie stattdessen direkt eine rekursive Funktion mit variablen Vorlagen schreiben.
quelle
initializer_list
verschoben werden kann und nicht, ob jemand Problemumgehungen hatte. Außerdem ist das Hauptverkaufsargument von,initializer_list
dass es nur für den Elementtyp und nicht für die Anzahl der Elemente verwendet wird und daher keine Empfänger erforderlich sind - und dies verliert dies vollständig.Ich habe eine viel einfachere Implementierung, die eine Wrapper-Klasse verwendet, die als Tag fungiert, um die Absicht zu markieren, die Elemente zu verschieben. Dies sind Kosten für die Kompilierung.
Die Wrapper - Klasse ist so konzipiert , in der Art und Weise verwendet werden ,
std::move
verwendet wird, ersetzen Sie einfachstd::move
mitmove_wrapper
, aber dies erfordert C ++ 17. Für ältere Spezifikationen können Sie eine zusätzliche Builder-Methode verwenden.Sie müssen Builder-Methoden / Konstruktoren schreiben, die Wrapper-Klassen akzeptieren,
initializer_list
und die Elemente entsprechend verschieben.Wenn Sie möchten, dass einige Elemente kopiert werden, anstatt verschoben zu werden, erstellen Sie eine Kopie, bevor Sie sie an übergeben
initializer_list
.Der Code sollte selbst dokumentiert sein.
quelle
Anstatt a zu verwenden
std::initializer_list<T>
, können Sie Ihr Argument als Array-Wertreferenz deklarieren:Siehe Beispiel unter Verwendung von
std::unique_ptr<int>
: https://gcc.godbolt.org/z/2uNxv6quelle
Betrachten Sie die
in<T>
auf cpptruths beschriebene Redewendung . Die Idee ist, lvalue / rvalue zur Laufzeit zu bestimmen und dann move oder copy-building aufzurufen.in<T>
erkennt rvalue / lvalue, obwohl die von initializer_list bereitgestellte Standardschnittstelle eine konstante Referenz ist.quelle