¿Qué son los comparadores transparentes?

106

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

Las plantillas de función miembro find, count, lower_bound, upper_bound, y equal_rangeno deberán participar en la resolución de sobrecarga menos que el tipo Compare::is_transparentexiste.

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

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, std::set<T, std::less<T>>que no tienen un comparador transparente, pero std::set<T, std::less<>> sería tener uno.

¿Qué problema resuelve esto y cambia el funcionamiento de los contenedores estándar? Por ejemplo, los parámetros de plantilla de std::setsiguen siendo Key, Compare = std::less<Key>, ..., también lo hace el conjunto predeterminado perder sus find, countetc. miembros?

Kerrek SB
fuente
Por ejemplo, consulte esta descripción de referencia de cpp . Y me siento estúpido ahora, porque estoy notando la palabra " plantilla de función miembro " ...
Kerrek SB
5
Posiblemente relacionado: stackoverflow.com/questions/18939882/…
cppreference también tiene una propaganda en en.cppreference.com/w/cpp/utility/functional/less_void
Cubbi

Respuestas:

60

¿Qué problema resuelve esto?

Ver 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 plantilla de función 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í. Ver N3465 de Joaquín Mª López Muñoz para la justificación y una propuesta detallada y cuidadosamente escrita para agregar esta característica.

En la reunión de Bristol, el LWG acordó 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 LWG 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 función. Decidimos que, aunque no sería seguro agregar la función incondicionalmente, sería seguro si estuviera deshabilitada de forma predeterminada y solo "habilitada".

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

Si no usa un "functor transparente" (es decir, uno que define un is_transparenttipo), los contenedores se comportan de la misma manera que siempre lo han hecho, y ese sigue siendo el predeterminado.

Si elige usar 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 las plantillas de alias:

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

El nombre is_transparentproviene del N3421 de STL que agregó los "operadores de diamante" a C ++ 14. Un "functor transparente" es uno que acepta cualquier tipo de argumento (que no tiene que ser el mismo) y simplemente reenvía esos argumentos a otro operador. Tal functor resulta ser exactamente lo que desea para la búsqueda heterogénea en contenedores asociativos, por lo que el tipo is_transparentse agregó a todos los operadores de diamante 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 "functor transparente", solo uno que admita llamarlo con tipos heterogéneos (por ejemplo, el pointer_comptipo en https://stackoverflow.com/a/18940595/981959 no es transparente según la definición de STL,pointer_comp::is_transparentpermite que se utilice para solucionar el problema). Si sólo las operaciones de búsqueda nunca en su std::set<T, C>con claves de tipo To intentonces Csólo tiene que haber exigible con argumentos de tipo Ty int(en cualquier orden), que no tiene por qué ser verdaderamente transparente. Usamos ese nombre en parte porque no pudimos encontrar un nombre mejor (hubiera preferido is_polymorphicporque tales functores usan polimorfismo estático, pero ya existe un std::is_polymorphicrasgo de tipo que se refiere al polimorfismo dinámico).

Jonathan Wakely
fuente
3
Oye, ¿fuiste tú a quien STL le dijo: "Por supuesto que puedes hacer una deducción de argumentos de plantilla en tu cabeza" en la charla vinculada a Woolstar?
Kerrek SB
10
No, yo no estaba allí, pero hay personas con muchos más compiladores conformes en la cabeza que yo :)
Jonathan Wakely
Supongo que "operador de diamante" se refiere <>en la propuesta vinculada, pero esa propuesta no se introdujo <>; es una sintaxis existente para una lista de parámetros de plantilla vacía. Los "operadores de diamante" serían un poco menos confusos.
Qwertie
33

En C ++ 11 no son plantillas miembros find(), lower_bound(), etc. Es decir, no se pierde nada por 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 por el ejemplo que es bueno y malo!

El is_transparentuso está destinado a evitar conversiones no deseadas. Si las plantillas de miembros no estuvieran restringidas, el código existente puede 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>uso de un literal de cadena: con la definición de C ++ 11, std::stringse construye un objeto cuando se pasan literales de cadena a la función miembro correspondiente. Con el cambio, es posible usar el literal de cadena directamente. Si el objeto de la 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 unstd::string y un literal de cadena, que 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 tratar con argumentos heterogéneos, define este tipo para indicar que puede tratar con 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 lo std::stringque se ha sobrecargado menos que los operadores que toman 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
fuente
Gracias, mira mi comentario sobre la otra pregunta: ¿Obtienes el comportamiento transparente por defecto?
Kerrek SB
8
@KerrekSB: el comportamiento transparente se habilita cuando is_transparentse define en el objeto de función de comparación de acuerdo con 23.2.4 [associative.reqmts] párrafo 13. Los objetos de función de comparación predeterminados son de std::less<Key>acuerdo con 23.4.2 [associative.map.syn] y 23.4. 3 [asociativo.set.syn]. De acuerdo con 20.10.5 párrafo [comparación] 4 la plantilla general para std::less<...>no no definir un tipo anidado is_transparentpero la std::less<void>especialización hace. Es decir, no, no obtiene un operador transparente por defecto.
Dietmar Kühl
¿Tienes alguna idea para el naming? Quiero decir por qué is_transparent?
plasmacel
¿Quiere un "ejemplo concreto en el que esto sea útil"? Este es mi caso de uso
SPRAFF
19

Lo siguiente es todo copy-pasta de n3657 .

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

A. Las funciones de búsqueda de contenedor asociativo (encontrar, lower_bound, upper_bound, equal_range) solo toman un argumento de key_type, lo que requiere que los usuarios construyan (implícita o explícitamente) un objeto de key_type para realizar la búsqueda. Esto puede resultar caro, 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 realizar búsquedas utilizando otros tipos que sean comparables con key_type.

P. ¿Qué problema resuelve esto?

A. El LWG tenía inquietudes sobre un código 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 carácter constante * a la función comparadora, 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 de plantilla find () también evitaría encontrar NULL en un contenedor de punteros, lo que hace que el código previamente válido ya no se compile, pero esto se consideró un problema menos serio que la regresión silenciosa del rendimiento.

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

A. Esta propuesta modifica los contenedores asociativos en y sobrecargando las funciones de miembros de búsqueda con plantillas de funciones de miembros. No hay cambios de idioma.

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

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

Para citar a Yakk ,

En C ++ 14, std :: set :: find es una función de plantilla si existe Compare :: is_transparent. El tipo que ingresa no necesita ser clave, solo equivalente en su comparador.

y n3657,

Agregue el párrafo 13 en 23.2.4 [associative.reqmts]: Las plantillas de función miembro find, lower_bound, upper_bound y equal_range no participarán en la resolución de sobrecarga a menos que exista el tipo Compare :: is_transparent does not exist.

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

El código completo está aquí .

Comunidad
fuente
1
¿ std::set<std::string>Realmente se beneficia de "pasar el char const *paso", o necesita hacer un std::set<std::string, std::less<>>?
Kerrek SB
@Kerrek Creo que "pasar la constante de caracteres *" era el problema que estaban tratando de evitar, si no me equivoco. Mire la redacción:With the change proposed by N3465 the std::set::find() function would be an unconstrained template which would pass the const char* through to the comparator function, std::less<std::string>, which would construct a std::string temporary for every comparison. The LWG considered this performance problem to be a serious issue.
Tu cita y la mía del párrafo 13 dicen lo contrario: "a menos que el tipo exista / no exista" ...?!
Kerrek SB
4
@KerrekSB, eso es mi culpa, se suponía que N3657 decía "existe" pero yo escribí "no existe" ... fue un documento tardío escrito en el último minuto. El proyecto de norma es correcto.
Jonathan Wakely
3
Sí, podría ser más claro citar lo que quise decir, no lo que dije en ese momento :)
Jonathan Wakely
7

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

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

Woolstar
fuente