¿Cuál es el propósito de C ++ 20 std :: common_reference?

Respuestas:

46

common_reference surgió de mis esfuerzos para llegar a una conceptualización de los iteradores de STL que se adapte a los iteradores proxy.

En el STL, los iteradores tienen dos tipos asociados de particular interés: referencey value_type. El primero es el tipo de retorno de los iteradores operator*, y el value_typees el tipo (sin constante, sin referencia) de los elementos de la secuencia.

Los algoritmos genéricos a menudo tienen la necesidad de hacer cosas como esta:

value_type tmp = *it;

... entonces sabemos que debe haber alguna relación entre estos dos tipos. Para los iteradores no proxy, la relación es simple: referencesiempre value_type, opcionalmente, const y referencia calificada. Los primeros intentos de definir el InputIteratorconcepto requerían que la expresión *itfuera convertible a const value_type &, y para la mayoría de los iteradores interesantes eso es suficiente.

Quería que los iteradores en C ++ 20 fueran más potentes que esto. Por ejemplo, considere las necesidades de un zip_iteratorque itera dos secuencias en el paso de bloqueo. Cuando desreferencia a zip_iterator, obtienes un temporal pairde los dos referencetipos de iteradores . Entonces, zip'a vector<int>y a vector<double>tendrían estos tipos asociados:

zipiterador reference: pair<int &, double &>
zipiterador value_type:pair<int, double>

Como puede ver, estos dos tipos no están relacionados entre sí simplemente agregando la calificación de cv y ref de nivel superior. Y sin embargo, dejar que los dos tipos sean arbitrariamente diferentes se siente mal. Claramente hay alguna relación aquí. Pero, ¿cuál es la relación y qué pueden asumir los algoritmos genéricos que operan en iteradores con seguridad sobre los dos tipos?

La respuesta en C ++ 20 es que para cualquier tipo de iterador válido, proxy o no, los tipos reference &&y value_type &comparten una referencia común . En otras palabras, para algún iterador ithay algún tipo CRque hace que lo siguiente esté bien formado:

void foo(CR) // CR is the common reference for iterator I
{}

void algo( I it, iter_value_t<I> val )
{
  foo(val); // OK, lvalue to value_type convertible to CR
  foo(*it); // OK, reference convertible to CR
}

CREs la referencia común. Todos los algoritmos pueden confiar en el hecho de que este tipo existe y pueden usarse std::common_referencepara calcularlo.

Entonces, ese es el papel que common_referencejuega en el STL en C ++ 20. En general, a menos que esté escribiendo algoritmos genéricos o iteradores proxy, puede ignorarlo con seguridad. Está allí debajo de las cubiertas para garantizar que sus iteradores cumplan con sus obligaciones contractuales.


EDITAR: El OP también pidió un ejemplo. Esto es un poco artificial, pero imagine que es C ++ 20 y se le da un rango rde tipo de acceso aleatorio Rsobre el cual no sabe nada y desea sortel rango.

Además, imagine que por alguna razón, desea utilizar una función de comparación monomórfica, como std::less<T>. (Tal vez ha borrado el rango, y necesita borrar también la función de comparación y pasarla a virtual? De nuevo, un tramo.) ¿Qué debería Thaber std::less<T>? Para eso usarías common_reference, o el ayudante iter_common_reference_tque se implementa en términos de ello.

using CR = std::iter_common_reference_t<std::ranges::iterator_t<R>>;
std::ranges::sort(r, std::less<CR>{});

Se garantiza que funcionará, incluso si el rango rtiene iteradores proxy.

Eric Niebler
fuente
2
Tal vez soy denso, pero ¿puedes aclarar cuál es la referencia común en el ejemplo de par zip?
happydave
44
Idealmente, pair<T&,U&>y pair<T,U>&tendría una referencia común, y sería simplemente pair<T&,U&>. Sin embargo, para std::pair, no hay conversión de pair<T,U>&a pair<T&,U&>aunque tal conversión es sólida en principio. (Esto, por cierto, es la razón por la que no tenemos una zipvista en C ++ 20.)
Eric Niebler
44
@EricNiebler: " Esto, por cierto, es la razón por la que no tenemos una vista zip en C ++ 20 " . ¿Hay alguna razón por la que un iterador zip tendría que usar pair, en lugar de un tipo que podría diseñarse específicamente para su propósito? , con conversiones implícitas apropiadas según sea necesario?
Nicol Bolas
55
@Nicol Bolas No hay necesidad de usar std::pair; cualquier tipo de par adecuado con las conversiones apropiadas funcionará, y range-v3 define dicho tipo de par. En el Comité, a LEWG no le gustó la idea de agregar a la Biblioteca Estándar un tipo que era casi, pero no del todo std::pair, sea normativo o no, sin primero hacer la debida diligencia sobre los pros y los contras de simplemente hacer el std::pairtrabajo.
Eric Niebler el
3
tuple` pair` tomato` to` MAH- to. pairtiene esta característica agradable con la que puede acceder a los elementos con .firsty .second. Los enlaces estructurados ayudan con la incomodidad de trabajar con tuples, pero no con todos.
Eric Niebler