Kann ich eine temporäre Leitung an eine Bereichsoperation zurückgeben?

9

Angenommen, ich habe eine generate_my_rangeKlasse, die a modelliert range(insbesondere ist regular). Dann ist der folgende Code korrekt:

auto generate_my_range(int some_param) {    
  auto my_transform_op = [](const auto& x){ return do_sth(x); };
  return my_custom_rng_gen(some_param) | ranges::views::transform(my_transform_op);
}
auto cells = generate_my_range(10) | ranges::to<std::vector>;

Wird my_custom_rng_gen(some_param)vom (ersten) Rohrbetreiber als Wert genommen, oder habe ich eine baumelnde Referenz, wenn ich den generate_my_rangeBereich verlasse ?

Wäre es mit dem Funktionsaufruf dasselbe ranges::views::transform(my_custom_rng_gen(some_param),my_transform_op)?

Wäre es richtig, wenn ich eine Wertreferenz verwenden würde? z.B:

auto generate_my_range(int some_param) {
  auto my_transform_op = [](const auto& x){ return do_sth(x); };
  auto tmp_ref = my_custom_rng_gen(some_param);
  return tmp_ref | ranges::views::transform(my_transform_op);
}

Wenn Bereiche für diese Operationen Werte annehmen, was mache ich dann, wenn ich einen lrefue ref an einen Container übergebe? Soll ich ein ranges::views::all(my_container)Muster verwenden?

Bérenger
quelle
Ist my_custom_rng_gen (some_param) bereits begrenzt? Meinst du so etwas wie godbolt.org/z/aTF8RN ohne die Aufnahme (5)?
Porsche9II
@ Porsche9II Ja das ist ein begrenzter Bereich. Nehmen wir an, es ist ein Container
Bérenger

Antworten:

4

In der Bereichsbibliothek gibt es zwei Arten von Operationen:

  • Ansichten, die faul sind und für die der zugrunde liegende Container vorhanden sein muss.
  • Aktionen, die eifrig sind und als Ergebnis neue Container produzieren (oder vorhandene modifizieren)

Ansichten sind leicht. Sie übergeben sie als Wert und verlangen, dass die zugrunde liegenden Container gültig und unverändert bleiben.

Aus der Dokumentation zu range-v3

Eine Ansicht ist ein kompakter Wrapper, der eine Ansicht einer zugrunde liegenden Folge von Elementen auf benutzerdefinierte Weise darstellt, ohne sie zu mutieren oder zu kopieren. Ansichten sind billig zu erstellen und zu kopieren und haben eine nicht besitzende Referenzsemantik.

und:

Jede Operation für den zugrunde liegenden Bereich, die seine Iteratoren oder Sentinels ungültig macht, macht auch jede Ansicht ungültig, die sich auf einen Teil dieses Bereichs bezieht.

Die Zerstörung des zugrunde liegenden Containers macht offensichtlich alle Iteratoren ungültig.

In Ihrem Code verwenden Sie speziell Ansichten - Sie verwenden ranges::views::transform. Die Pfeife ist lediglich ein syntaktischer Zucker, um das Schreiben so zu erleichtern, wie es ist. Sie sollten sich das Letzte in der Pfeife ansehen, um zu sehen, was Sie produzieren - in Ihrem Fall ist es eine Ansicht.

Wenn es keinen Rohrbetreiber gäbe, würde es wahrscheinlich ungefähr so ​​aussehen:

ranges::views::transform(my_custom_rng_gen(some_param), my_transform_op)

Wenn auf diese Weise mehrere Transformationen verbunden wären, können Sie sehen, wie hässlich es werden würde.

Wenn also my_custom_rng_geneine Art Container erzeugt wird, den Sie transformieren und dann zurückgeben, wird dieser Container zerstört und Sie haben baumelnde Referenzen aus Ihrer Sicht. Wenn my_custom_rng_genes sich um eine andere Ansicht eines Containers handelt, der außerhalb dieser Bereiche lebt, ist alles in Ordnung.

Der Compiler sollte jedoch erkennen können, dass Sie eine Ansicht auf einen temporären Container anwenden, und Sie mit einem Kompilierungsfehler treffen.

Wenn Ihre Funktion einen Bereich als Container zurückgeben soll, müssen Sie das Ergebnis explizit "materialisieren". Verwenden Sie dazu den ranges::toOperator innerhalb der Funktion.


Update: Um Ihren Kommentar genauer zu beschreiben : "Wo steht in der Dokumentation, dass das Erstellen von Bereichen / Rohrleitungen eine Ansicht benötigt und speichert?"

Pipe ist lediglich ein syntaktischer Zucker, um Dinge in einem leicht lesbaren Ausdruck zu verbinden. Je nachdem, wie es verwendet wird, wird möglicherweise eine Ansicht zurückgegeben oder nicht. Es kommt auf das Argument auf der rechten Seite an. In Ihrem Fall ist es:

`<some range> | ranges::views::transform(...)`

Der Ausdruck gibt also alles views::transformzurück, was zurückgegeben wird.

Lesen Sie nun die Dokumentation der Transformation:

Unten finden Sie eine Liste der Lazy Range-Kombinatoren oder -Ansichten, die Range-v3 bereitstellt, sowie einen Klappentext darüber, wie die einzelnen Funktionen verwendet werden sollen.

[...]

views::transform

Geben Sie bei einem gegebenen Quellbereich und einer unären Funktion einen neuen Bereich zurück, in dem jedes Ergebniselement das Ergebnis der Anwendung der unären Funktion auf ein Quellelement ist.

Es gibt also einen Bereich zurück, aber da es sich um einen Lazy-Operator handelt, handelt es sich bei dem zurückgegebenen Bereich um eine Ansicht mit all ihrer Semantik.

CygnusX1
quelle
OK. Was für mich immer noch etwas mysteriös ist, ist, wie es funktioniert, wenn ich einen Container an die Pipe weitergebe (dh das von der Komposition erstellte Bereichsobjekt). Es muss irgendwie eine Ansicht des Containers speichern. Ist es fertig mit ranges::views::all(my_container)? Und was ist, wenn ein Blick auf die Pipe übertragen wird? Erkennt es, dass ein Container oder eine Ansicht übergeben wurde? Muss es sein? Wie?
Bérenger
"Der Compiler sollte erkennen können, dass Sie eine Ansicht auf einen temporären Container anwenden, und Sie mit einem Kompilierungsfehler treffen." Das dachte ich auch: Wenn ich etwas Dummes mache, bedeutet dies einen Vertrag über den Typ (als Linker) Wert) ist nicht erfüllt. Solche Sachen macht Range-v3. Aber in diesem Fall gibt es absolut kein Problem. Es kompiliert UND läuft. Es kann also undefiniertes Verhalten geben, das jedoch nicht angezeigt wird.
Bérenger
Um sicherzugehen, ob Ihr Code versehentlich korrekt ausgeführt wird oder ob alles in Ordnung ist, muss ich den Inhalt von sehen my_custom_rng_gen. Wie genau das Rohr und die transformInteraktion unter der Haube sind nicht wichtig. Der gesamte Ausdruck verwendet einen Bereich als Argument (einen Container oder eine Ansicht für einen Container) und gibt eine andere Ansicht für diesen Container zurück. Der Rückgabewert wird niemals den Container besitzen, da es sich um eine Ansicht handelt.
CygnusX1
1

Entnommen aus der Dokumentation zu range-v3 :

Ansichten haben [...] keine Referenzsemantik.

und

Ein einziges Bereichsobjekt ermöglicht Pipelines von Operationen. In einer Pipeline wird ein Bereich träge angepasst oder auf irgendeine Weise eifrig mutiert, wobei das Ergebnis sofort für eine weitere Anpassung oder Mutation verfügbar ist. Die verzögerte Anpassung wird durch Ansichten und die eifrige Mutation durch Aktionen behandelt.

// taken directly from the the ranges documentation
std::vector<int> const vi{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
using namespace ranges;
auto rng = vi | views::remove_if([](int i){ return i % 2 == 1; })
              | views::transform([](int i){ return std::to_string(i); });
// rng == {"2","4","6","8","10"};

Im obigen Code speichert rng einfach einen Verweis auf die zugrunde liegenden Daten sowie die Filter- und Transformationsfunktionen. Es wird keine Arbeit erledigt, bis rng iteriert wird.

Da Sie gesagt haben, dass der temporäre Bereich als Container betrachtet werden kann, gibt Ihre Funktion eine baumelnde Referenz zurück.

Mit anderen Worten, Sie müssen sicherstellen, dass der zugrunde liegende Bereich die Ansicht überlebt, oder Sie haben Probleme.

Rumburak
quelle
Ja, Ansichten gehören nicht, aber wo steht in der Dokumentation, dass beim Erstellen von Bereichen / Rohrleitungen eine Ansicht erstellt und gespeichert wird? Es wäre möglich (und ich denke, eine gute Sache), die folgende Richtlinie zu haben: Nach Wert speichern, wenn der Bereich durch eine rWert-Referenz angegeben wird.
Bérenger
1
@ Bérenger Ich habe etwas mehr aus der Bereichsdokumentation hinzugefügt. Aber der Punkt ist wirklich: Eine Ansicht ist nicht besitzergreifend . Es ist egal, ob Sie ihm einen Wert geben.
Rumburak