¿Qué son los comparadores transparentes?

Resuelto Kerrek SB asked hace 10 años • 4 respuestas

En C++14, los contenedores asociativos parecen haber cambiado desde C++11 – [associative.reqmts]/13 dice:

Las plantillas de funciones miembro find, count, lower_bound, upper_boundy equal_rangeno participarán en la resolución de sobrecarga a menos que el tipo Compare::is_transparentexista.

¿Cuál es el objetivo de hacer "transparente" un comparador?

C++14 también proporciona plantillas de biblioteca como esta:

template <class T = void> struct less {
    constexpr bool operator()(const T& x, const T& y) const;
    typedef T first_argument_type;
    typedef T second_argument_type;
    typedef bool result_type;
};

template <> struct less<void> {
    template <class T, class U> auto operator()(T&& t, U&& u) const
    -> decltype(std::forward<T>(t) < std::forward<U>(u));
    typedef *unspecified* is_transparent;
};

Así por ejemplo, nostd::set<T, std::less<T>> tendríamos un comparador transparente, pero sí lo tendríamos .std::set<T, std::less<>>

¿Qué problema resuelve esto? ¿Cambia esto el funcionamiento de los contenedores estándar? Por ejemplo, los parámetros de la plantilla de std::settodavía son Key, Compare = std::less<Key>, ..., entonces, ¿el conjunto predeterminado pierde sus miembros find, count, etc.?

Kerrek SB avatar Dec 02 '13 04:12 Kerrek SB
Aceptado

¿Qué problema resuelve esto?

Vea la respuesta de Dietmar y la respuesta de remyabel .

¿Y esto cambia el funcionamiento de los contenedores estándar?

No, no por defecto.

Las nuevas sobrecargas de plantillas de funciones miembro de findetc. le permiten usar un tipo que es comparable con la clave del contenedor, en lugar de usar el tipo de clave en sí. Véase N3465 de Joaquín Mª López Muñoz para conocer los fundamentos y una propuesta detallada y cuidadosamente escrita para agregar esta característica.

En la reunión de Bristol, el GTL estuvo de acuerdo en que la función de búsqueda heterogénea era útil y deseable, pero no podíamos estar seguros de que la propuesta de Joaquín fuera segura en todos los casos. La propuesta N3465 habría causado serios problemas para algunos programas (consulte la sección Impacto en el código existente ). Joaquín preparó un borrador de propuesta actualizado con algunas implementaciones alternativas con diferentes compensaciones, lo cual fue muy útil para ayudar al GTL a comprender los pros y los contras, pero todos corrían el riesgo de romper algunos programas de alguna manera, por lo que no hubo consenso para agregar la característica. Decidimos que, aunque no sería seguro agregar la función incondicionalmente, sería seguro si estuviera deshabilitada de forma predeterminada y solo "optar por participar".

La diferencia clave de la propuesta N3657 (que fue una revisión de último minuto realizada por mí y por STL basada en N3465 y un borrador inédito posterior de Joaquín) fue agregar el is_transparenttipo como protocolo que se puede usar para optar por la nueva funcionalidad.

Si no utiliza un "funtor transparente" (es decir, uno que define un is_transparenttipo), los contenedores se comportan igual que siempre, y ese sigue siendo el valor predeterminado.

Si elige utilizar std::less<>(que es nuevo para C++14) u otro tipo de "functor transparente", obtendrá la nueva funcionalidad.

Usar std::less<>es fácil con plantillas de alias:

template<typename T, typename Cmp = std::less<>, typename Alloc = std::allocator<T>>
  using set = std::set<T, Cmp, Alloc>;

El nombre proviene del N3421is_transparent de STL que agregó los "operadores de diamante" a C++14. Un "funtor transparente" es aquel que acepta cualquier tipo de argumento (que no tiene que ser el mismo) y simplemente reenvía esos argumentos a otro operador. Resulta que dicho funtor es exactamente lo que desea para búsquedas heterogéneas en contenedores asociativos, por lo que el tipo se agregó a todos los operadores de diamantes y se usó como tipo de etiqueta para indicar que la nueva funcionalidad debe habilitarse en contenedores asociativos. Técnicamente, los contenedores no necesitan un "funtor transparente", solo uno que admita llamarlo con tipos heterogéneos (por ejemplo, el tipo en https://stackoverflow.com/a/18940595/981959 no es transparente según la definición de STL, pero definir permite utilizarlo para resolver el problema). Si solo busca claves de tipo o solo necesita ser invocable con argumentos de tipo y (en cualquier orden), no es necesario que sea verdaderamente transparente. Usamos ese nombre en parte porque no se nos ocurrió un nombre mejor (hubiera preferido porque dichos functores usan polimorfismo estático, pero ya existe un rasgo de tipo que se refiere al polimorfismo dinámico).is_transparentpointer_comppointer_comp::is_transparentstd::set<T, C>TintCTintis_polymorphicstd::is_polymorphic

Jonathan Wakely avatar Dec 04 '2013 18:12 Jonathan Wakely

En C++11 no hay plantillas de miembros find(), lower_bound()etc. Es decir, no se pierde nada con este cambio. Las plantillas de miembros se introdujeron con n3657 para permitir el uso de claves heterogéneas con los contenedores asociativos. ¡No veo ningún ejemplo concreto en el que esto sea útil, excepto el ejemplo que es bueno y malo!

El is_transparentuso tiene como objetivo evitar conversiones no deseadas. Si las plantillas de miembros no tuvieran restricciones, el código existente podría pasar directamente a través de objetos que se habrían convertido sin las plantillas de miembros. El caso de uso de ejemplo de n3657 es ubicar un objeto en un std::set<std::string>usando un literal de cadena: con la definición de C++11, un std::stringobjeto se construye al pasar un literal de cadena a la función miembro correspondiente. Con el cambio es posible utilizar el literal de cadena directamente. Si el objeto de función de comparación subyacente se implementa exclusivamente en términos de std::stringeso, es malo porque ahora std::stringse crearía un para cada comparación. Por otro lado, si el objeto de función de comparación subyacente puede tomar un std::stringliteral de cadena y un literal, eso puede evitar la construcción de un objeto temporal.

El is_transparenttipo anidado en el objeto de función de comparación proporciona una manera de especificar si se debe usar la función miembro con plantilla: si el objeto de función de comparación puede manejar argumentos heterogéneos, define este tipo para indicar que puede manejar diferentes argumentos de manera eficiente. Por ejemplo, los nuevos objetos de función de operador simplemente delegan operator<()y afirman ser transparentes. Eso, al menos, funciona para std::stringlos que se han sobrecargado menos que los operadores tomando char const*como argumento. Dado que estos objetos de función también son nuevos, incluso si hacen algo incorrecto (es decir, requieren una conversión para algún tipo), al menos no sería un cambio silencioso que resulte en una degradación del rendimiento.

Dietmar Kühl avatar Dec 01 '2013 22:12 Dietmar Kühl

Lo siguiente es todo copy-pasta de n3657 .

P. ¿Cuál es el propósito de hacer que un comparador sea "transparente"?

R. Las funciones de búsqueda de contenedores asociativos (buscar, límite_inferior, límite_superior, rango_igual) solo toman un argumento de tipo_clave, lo que requiere que los usuarios construyan (ya sea implícita o explícitamente) un objeto de tipo_clave para realizar la búsqueda. Esto puede resultar costoso, por ejemplo, construir un objeto grande para buscar en un conjunto cuando la función de comparación sólo mira un campo del objeto. Existe un gran deseo entre los usuarios de poder buscar utilizando otros tipos que sean comparables con key_type.

P. ¿ Qué problema resuelve esto?

R. El LWG tenía preocupaciones sobre códigos como el siguiente:

std::set<std::string> s = /* ... */;
s.find("key");

En C++11, esto construirá un único std::string temporal y luego lo comparará con elementos para encontrar la clave.

Con el cambio propuesto por N3465, la función std::set::find() sería una plantilla sin restricciones que pasaría el const char* a la función de comparación, std::less, que construiría un std::string temporal para cada comparación. El LWG consideró que este problema de rendimiento era un problema grave. La función find() de plantilla también evitaría encontrar NULL en un contenedor de punteros, lo que provoca que el código previamente válido ya no se compile, pero esto se consideró un problema menos grave que la regresión silenciosa del rendimiento.

P. ¿Esto cambia el funcionamiento de los contenedores estándar?

R. Esta propuesta modifica los contenedores asociativos al sobrecargar las funciones miembro de búsqueda con plantillas de funciones miembro. No hay cambios de idioma.

P. ¿ El conjunto predeterminado también pierde sus miembros de búsqueda, recuento, etc.?

R. Casi todo el código C++11 existente no se ve afectado porque las funciones miembro no están presentes a menos que se utilicen nuevas características de la biblioteca C++14 como funciones de comparación.

Para citar a Yakk ,

En C++14, std::set::find es una función de plantilla si Compare::is_transparent existe. No es necesario que el tipo que ingrese sea clave, solo equivalente en su comparador.

y n3657,

Agregue el párrafo 13 en 23.2.4 [associative.reqmts]: Las plantillas de funciones miembro find, lower_bound, Upper_bound e igual_range no participarán en la resolución de sobrecarga a menos que exista el tipo Compare::is_transparent is not exist .

n3421 proporciona un ejemplo de "Functores de operador transparentes" .

El código completo está aquí .

 avatar Dec 01 '2013 23:12

Stephan T Lavavej habla sobre los problemas en los que el compilador sigue creando temporales y cómo su propuesta de funtores de operador transparentes resolverá esto en c++1y.

GoingNative 2013: no ayude al compilador (aproximadamente a la hora)

woolstar avatar Dec 04 '2013 18:12 woolstar