Cómo implementar std::conjunction y std::disjunction en C++11

Cómo implementar std::conjunction y std::disjunction en C++11

Entre las muchas características que introdujo C++17, la biblioteca estándar obtuvo std::conjunction y su hermano (¿o es una hermana?) std::disjunction .

std::conjunction permite realizar un AND lógico en un paquete variado de valores booleanos, y std::disjunction un OR lógico:

std::conjunction<Bs...>::value // is true if all Bs... are true, false otherwise

std::disjunction<Bs...>::value // is true if at least one of Bs... is true, false otherwise

Esos ayudantes convenientes simplifican el código de la plantilla. Sería bueno tener esta función disponible incluso si aún no estás en C++17.

Resulta que podemos implementarlo con bastante facilidad en C++11. Pero antes de ver cómo implementarlo, comencemos por ver cómo no para implementarlo.

Cómo no implementar std::conjunction en C++11

Quizás se pregunte qué sentido tiene ver una forma incorrecta de implementar std::conjunction en C++11. La razón por la que esto es interesante es que muestra un antipatrón de plantilla variable que todos debemos tener en cuenta:recursión .

De hecho, el uso de recursividad a menudo se considera una mala práctica cuando se trata de manipular plantillas variádicas. La razón es que si el paquete es lo suficientemente grande, esto se vuelve engorroso para el compilador y puede ralentizar la compilación.

Como muchas cosas en C++, no significa que nunca debamos hacer recursividad con plantillas variádicas. Más bien significa que siempre debemos intentar para escribir código de plantilla variable sin usar recursividad.

Lo que pasa es que la recursividad es a veces la primera solución que se nos ocurre. Si no hubiera sido por mi amigo Sy Brand que me mostró una solución mejor, no habría sabido cómo implementar conjunction que no sea con el siguiente código:

template<class...> struct conjunction : std::true_type { };

template<class B1> struct conjunction<B1> : B1 { };

template<class B1, class... Bn>
struct conjunction<B1, Bn...> 
    : std::conditional<bool(B1::value), conjunction<Bn...>, B1>::type {};

Esta es más o menos la implementación sugerida en cppreference.com.

Podemos ver la recursión aquí:el código define los casos de 0 y 1 parámetro, y luego un patrón de cabeza-cola donde definimos conjunction de la cabeza-cola llamando a conjunction en la cola En el código anterior, B1 es la cabeza y Bn... es la cola.

Este código es muy natural y expresivo, pero utiliza el antipatrón de recursividad para plantillas variádicas.

¿Puedes ver cómo implementar conjunction? sin recursividad?

¡Vamos, pruébalo!

Si encuentra algo, deje su solución en un comentario, me encantaría leerlo.

¿Ya has terminado, estás listo para leer sobre la forma no recursiva de Sy?

Cómo implementar std::conjunction en C++11

Aquí hay una manera astuta de implementar conjunction en C++11 y sin recursividad. Veamos el código y expliquémoslo después:

template<bool...> struct bool_pack{};

template<bool... Bs>
using conjunction = std::is_same<bool_pack<true,Bs...>, bool_pack<Bs..., true>>;

Esta es una pieza de código bastante compacta. Veamos cómo funciona esto.

bool_pack es una plantilla que contiene un paquete variado de booleanos. El struct en sí mismo no tiene miembros de datos o funciones. Su único propósito es contener su paquete de booleanos. De ahí el nombre, bool_pack .

El paquete contiene todos los booleanos a los que nos gustaría aplicar un AND lógico, más uno:

std::is_same compara los tipos, lo que incluye comparar los respectivos parámetros de plantilla. Entonces si bool_pack<true, Bs...> y bool_pack<Bs..., true> son del mismo tipo, significa que:

  • B1 == true ,
  • B2 == B1 , lo que significa que B2 == true ,
  • B3 == B2 , lo que significa que B3 == true ,
  • Bn == B(n-1) , lo que significa que Bn == true .

El último true del segundo paquete es redundante, pero tiene que estar aquí porque de lo contrario los dos bool_pack no tendría el mismo número de parámetros de plantilla, y std::is_same devolvería false .

Sin recursividad

Tenga en cuenta cómo esta implementación de conjunction no usa recursividad. En su lugar, se basa en la capacidad del compilador para comparar cada elemento correspondiente de dos paquetes variados.

std::disjunction

Para implementar std::conjunction , confiamos en el compilador que compara los paquetes variados, lo que garantiza que TODOS los tipos sean iguales. Organizamos los paquetes para garantizar que TODOS los valores booleanos sean iguales a verdaderos.

¿Podemos aplicar la misma técnica para implementar std::disjunction? ?

std::disjunction parece tener una necesidad diferente. Al contrario de conjunction donde queremos que TODOS los booleanos sean verdaderos, para disjunction necesitamos AL MENOS UN valor booleano para que sea verdadero. Parece más difícil confiar en que el compilador compare tipos variados para esto.

¿Cómo implementarías disjunction? en C++ 11? Por favor, deje un comentario a continuación.

Una forma de implementar disjunction es reutilizar conjunction . De hecho, otra forma de expresar que AL MENOS UN booleano es verdadero es que es falso que TODOS ellos son falsos.

Así es como se vería en el código:

template <bool B>
using bool_constant = std::integral_constant<bool, B>; // redefining C++17 bool_constant helper

template<bool... Bs>
struct disjunction : bool_constant<!conjunction<!Bs...>::value>{};

Esto permite implementar disjunction en C++11 sin usar recursividad.

Avanzando hacia el futuro

Si está en C++ 11, o en C++ 14, o en cualquier otra versión de C++ que no sea la última disponible, es importante que actualice su compilador y plataforma para poder acceder a la última versión de C++ disponible. Cada versión reciente ha agregado innumerables características para escribir código más expresivo.

Pero actualizar el compilador puede ser un proceso largo, especialmente en grandes bases de código heredadas, o si tiene dependencias con clientes, o por cualquier otra razón.

Mientras tanto, antes de realizar la actualización, no tiene que limitarse a las características de un estándar antiguo. Con conjunction y disjunction , tenemos un ejemplo más de que podemos escribir código moderno y que hay cosas que aprender cualquiera que sea la versión de C++ que estemos usando.

También te gustará

  • Algoritmos sobre rangos
  • Cómo definir un número variable de argumentos del mismo tipo
  • Cómo C++17 se beneficia de las bibliotecas Boost, segunda parte
  • Cppcast:un espectáculo para todos los desarrolladores de C++
  • Más allá de los candados, una forma más segura y expresiva de lidiar con Mutexes en C++