Supongamos que tengo una generate_my_range
clase que modela a range
(en particular, es regular
). Entonces es el siguiente código correcto:
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>;
¿Es my_custom_rng_gen(some_param)
tomado por valor por el (primer) operador de tubería, o tengo una referencia colgante una vez que salgo del generate_my_range
alcance?
¿Sería lo mismo con la llamada funcional ranges::views::transform(my_custom_rng_gen(some_param),my_transform_op)
?
¿Sería correcto si usara una referencia lvalue? p.ej:
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);
}
Si se toman rangos por valores para estas operaciones, ¿qué debo hacer si paso una referencia de valor a un contenedor? ¿Debo usar un ranges::views::all(my_container)
patrón?
Respuestas:
En la biblioteca de rangos hay dos tipos de operaciones:
Las vistas son ligeras. Los pasa por valor y requiere que los contenedores subyacentes permanezcan válidos y sin cambios.
De la documentación de la gama v3
y:
La destrucción del contenedor subyacente obviamente invalida todos los iteradores.
En su código, está utilizando vistas específicamente : usted usa
ranges::views::transform
. La tubería es simplemente un azúcar sintáctico para que sea fácil escribir de la forma en que está. Debería mirar lo último en la tubería para ver lo que produce, en su caso, es una vista.Si no hubiera operador de tubería, probablemente se vería así:
si hubiera múltiples transformaciones conectadas de esa manera, puede ver lo feo que se pondría.
Por lo tanto, si
my_custom_rng_gen
produce algún tipo de contenedor, que transforma y luego devuelve, ese contenedor se destruye y tiene referencias colgantes desde su vista. Simy_custom_rng_gen
es otra vista de un contenedor que vive fuera de estos ámbitos, todo está bien.Sin embargo, el compilador debería poder reconocer que está aplicando una vista en un contenedor temporal y recibir un error de compilación.
Si desea que su función devuelva un rango como contenedor, debe "materializar" explícitamente el resultado. Para eso, use el
ranges::to
operador dentro de la función.Actualización: para ser más explícito con respecto a su comentario "¿dónde dice la documentación que componer el rango / tubería toma y almacena una vista?"
Pipe es simplemente un azúcar sintáctico para conectar cosas en una expresión fácil de leer. Dependiendo de cómo se use, puede o no devolver una vista. Depende del argumento del lado derecho. En tu caso es:
Entonces la expresión devuelve lo que sea que
views::transform
regrese.Ahora, leyendo la documentación de la transformación:
Por lo tanto, devuelve un rango, pero dado que es un operador perezoso, ese rango que devuelve es una vista, con toda su semántica.
fuente
ranges::views::all(my_container)
? ¿Y qué pasa si se pasa una vista a la tubería? ¿Reconoce que se le pasa un contenedor o una vista? ¿Necesita hacerlo? ¿Cómo?my_custom_rng_gen
. Cómo exactamente la tubería etransform
interactuar bajo el capó no es importante. La expresión completa toma un rango como argumento (un contenedor o una vista para algún contenedor) y devuelve una vista diferente a ese contenedor. El valor de retorno nunca será el propietario del contenedor, porque es una vista.Tomado de la documentación degamas-v3 :
y
Como usted dijo que el rango temporal puede considerarse como un contenedor, su función devuelve una referencia colgante.
En otras palabras, debe asegurarse de que el rango subyacente sobreviva a la vista, o está en problemas.
fuente