Range-v3 interdit les vues sur les conteneurs temporaires pour nous aider à éviter la création d'itérateurs en suspens. Votre exemple démontre exactement pourquoi cette règle est nécessaire dans les compositions de vues :
auto rng = src | view::transform(f) | view::join;
Si view::join
devaient stocker les begin
y end
itérateurs du vecteur temporaire renvoyé par f
ils seraient invalidés avant même d'être utilisés.
"C'est très bien, Casey, mais pourquoi les vues range-v3 ne stockent-elles pas des plages temporaires comme celle-ci en interne ?
Parce que la performance. Tout comme la performance des algorithmes STL repose sur l'exigence que les opérations sur les itérateurs soient O(1), la performance des compositions de vues repose sur l'exigence que les opérations sur les vues soient O(1). Si les vues devaient stocker des plages temporaires dans des conteneurs internes "derrière votre dos", la complexité des opérations de vue - et donc des compositions - deviendrait imprévisible.
"D'accord, très bien. Étant donné que je comprends toute cette merveilleuse conception, comment puis-je faire en sorte que cela fonctionne ?!??"
Puisque la composition de la vue ne stocke pas les plages temporaires pour vous, vous devez les placer vous-même dans une sorte de stockage, par exemple :
#include <iostream>
#include <vector>
#include <range/v3/range_for.hpp>
#include <range/v3/utility/functional.hpp>
#include <range/v3/view/iota.hpp>
#include <range/v3/view/join.hpp>
#include <range/v3/view/transform.hpp>
using T = int;
std::vector<T> f(T t) { return std::vector<T>(2, t); }
int main() {
std::vector<T> buffer;
auto store = [&buffer](std::vector<T> data) -> std::vector<T>& {
return buffer = std::move(data);
};
auto rng = ranges::view::ints
| ranges::view::transform(ranges::compose(store, f))
| ranges::view::join;
unsigned count = 0;
RANGES_FOR(auto&& i, rng) {
if (count) std::cout << ' ';
else std::cout << '\n';
count = (count + 1) % 8;
std::cout << i << ',';
}
}
Notez que l'exactitude de cette approche dépend du fait que view::join
est une plage d'entrée et donc un passage unique.
"Ce n'est pas facile pour les novices. En fait, ce n'est pas adapté aux experts. Pourquoi n'y a-t-il pas une sorte de prise en charge du 'stockage temporaire materialization™' dans range-v3 ?"
Parce que nous n'avons pas encore eu le temps de le faire - les correctifs sont les bienvenus ;)
0 votes
Toutes mes excuses pour la réponse incroyablement tardive, j'avais oublié que cette question existait.
1 votes
Les astuces ci-dessous ne devraient pas être nécessaires. J'ai ma propre bibliothèque de gammes qui résout le problème. Si, lors de la composition de l'arbre d'expression, les conteneurs sources sont transmis en tant que lvalue, une référence est stockée. Si le conteneur source est passé en tant que rvalue, il est déplacé dans l'arbre d'expression. Tous les noeuds de l'arbre d'expression supportent la sémantique de déplacement, donc tant que vous construisez l'arbre d'expression sans copie réelle, vous n'obtenez que des déplacements du conteneur source. J'ai commencé avec exactement le même problème que range-v3, mais il peut être résolu.
1 votes
Quelques tests unitaires montrant que les valeurs l et les valeurs r sont gérées correctement. Malheureusement je ne peux pas partager plus car cela fait partie d'une base de code propriétaire :( gist.github.com/bradphelan/0bb9397ea7b49f45122908b1a9da061f
1 votes
En fait, je suis venu ici pour chercher la réponse à une question légèrement différente, que j'ai confondue avec cette question. Dans mon cas, j'ai un temporaire au début de mon pipeline :
for (int i : std::vector({1,2,3}) | mytransform([](int i) { return i+1; })) { std::cout << i << std::endl; }
. Ma fonction mytransform produisait des déchets ou se plantait parce que la fonctionstd::vector
est supprimé prématurément (la durée de vie du rhs temporaire d'un range-based-for-loop est prolongée jusqu'à la fin de la boucle, mais tous les autres temps ne sont pas prolongés :-( ) Les commentaires de @bradgonesurfing ci-dessus résolvent ce problème de façon brillante.0 votes
Mise en garde non évidente : dans le cas d'un déplacement, le conteneur src déplacé doit être placé dans un shared_ptr afin que toutes les copies de la vue partagent la source et que la copie de la vue soit O(1).
0 votes
Bizarre. J'ai cloné range-v3 hier mais j'obtiens toujours cette erreur, malgré la mise à jour disant que cela a été corrigé. Je suis sur clang.
0 votes
@DonHatch : Je suis surpris que vous ayez réussi à compiler votre exemple avec un temporary au début du pipe. clang génère une erreur lorsque j'essaie de faire cela.