💫 Conga no vuelve a la base

💎 Conga no vuelve a la base online

✌️ std::span vs std::array

Este post es una respuesta a RangeOf: A better span, que tiene muchos problemas que vale la pena tratar en detalle. Mientras que la mayor parte de este post tratará específicamente de std::span<T> (que es, de hecho, el mejor span), la última sección también discutirá una reciente adición a la biblioteca estándar: std::ranges::subrange<T*>.
El post original presenta tres “problemas” con span. Uno de ellos es el de la denominación – pero nombrar es difícil y la gente puede no estar de acuerdo en cuál podría ser el nombre correcto para tal cosa. span me parece bien. En mi base de código en el trabajo, lo llamamos Slice<T> (llamado así por el concepto de Rust del mismo nombre), aunque este nombre no funcionaría tan bien en C++ ya que ya hay una cosa en la biblioteca estándar llamada std::slice<T>.
La tercera es sobre cómo span sólo puede ser usado sobre memoria contigua. Es importante notar que la creación de una vista contigua con borrado de tipo tiene cero sobrecarga ya que puedes representar completamente el estado con básicamente un par<T*, size_t> (más sobre esto más adelante). Pero crear una vista no contigua borrada de tipo puede ser extremadamente caro. span es sólo para rangos contiguos porque es precisamente la falta de sobrecarga lo que lo hace tan útil.

->  Bateria portatil para ordenador

👌 std::span

Tengo problemas para encontrar la semántica del rasgo de tipo de referencia de un iterador. Digamos que quiero implementar un iterador de trozos, que, dada una posición en un rango, me dé trozos de ese rango:
El problema que veo aquí es que std::span es algo parecido a una vista, pero no se comporta como una referencia (digamos un std::array<T,N>& en este caso). En particular, si asigno a un span, la asignación es superficial, no copiará el valor.
(nota: resolver el problema almacenando un std::array<T,N> y devolviendo un std::array<T,N>& en operator* es factible, pero feo, y si N no se conoce en tiempo de compilación, almacenar en su lugar un std::vector<T> con asignación dinámica de memoria es simplemente erróneo)
Para los Iteradores conformes, casi no importa cuál es el tipo de referencia porque el estándar no requiere ninguna semántica de uso para el tipo de referencia. Pero eso también significa que nadie, excepto tú, sabe cómo usar tu iterador.
Para los Iteradores de Entrada conformes, el tipo de referencia debe cumplir con la semántica especificada. Tenga en cuenta que para LegacyInputIterator, la expresión *it debe ser una referencia utilizable como referencia con toda la semántica normal, de lo contrario el código que utilice su iterador no se comportará como se espera. Esto significa que la lectura de una referencia es similar a la lectura de una referencia incorporada. En particular, lo siguiente debería hacer cosas “normales”:

👇 ejemplo de std::span

El objetivo de std::span<T> es ser una vista sobre datos contiguos. pair<T*, size_> (o algo parecido) es la forma correcta de representar esa vista. No puedes tener un std::span que sea una vista sobre un std::list o un std::map, así que no tiene sentido inventar una forma de representarlo. El punto es que sea un tipo común, de vocabulario, que sólo acepte datos contiguos.
También es muy importante que span esté efectivamente borrado de tipo. Un span<int> podría referirse a un int[20] o a un vector<int> o a un int[] que se asigna dinámicamente en algún lugar o a un llvm::SmallVector<int> o a un … No importa de dónde venga, sólo tienes el tipo que sea: “vista sobre algunos ints contiguos”.
Es cierto que pair<Iter, Iter> (o, más generalmente, pair<Iter, Sentinel>) es una representación más general que funcionaría para más contenedores. También existe algo así en C++20, se llama std::ranges::subrange<I, S>. Pero ten en cuenta que aquí no tenemos el aspecto de la certeza de tipo… un subrango sobre un mapa<K, V> tendrá un tipo diferente que un subrango sobre un contenedor diferente con el mismo tipo_valor, como lista<par<K const, V>> o vector<par<K const, V>> o multimapa<K, V>.

->  Ordenador todo en uno