Proyecciones con Boost.HOF

La biblioteca header-only Boost.HOF [1], acrónimo de Higher Order Functions, constituye una útil herramienta de programación funcional en C++ que, como su nombre indica, opera con funciones de orden superior --es decir, funciones que toman otras funciones como entrada y/o que devuelven otra función como salida. La biblioteca proporciona múltiples utilidades para funciones y objetos función que buscan resolver de forma sencilla problemas que, tradicionalmente, han sido abarcados con técnicas de metaprogramación más complejas (piénsese, por ejemplo, en SFINAE).

En este post analizaremos la funcionalidad del adaptador de funciones proj [2], que aplica proyecciones unarias sobre los argumentos de otra función. Disponemos de dos sobrecargas en el espacio de nombres boost::hof:

   template<typename Projection, typename F>    constexpr auto proj(Projection p, F f) -> proj_adaptor<Projection, F>;    template<typename Projection>    constexpr auto proj(Projection p) -> proj_adaptor<Projection>;

donde se asume que el tipo F es ConstInvocable [3].

La semántica de los adaptadores anteriores viene dada, respectivamente, por:

   assert(proj(p, f)(xs...) == f(p(xs)...));    assert(proj(p)(xs...) == p(xs)...);

En el primer caso --centro de interés de este artículo--, el adaptador proporciona a la función f la posibilidad de operar sobre los argumentos proyectados p(xs)..., en sustitución de los xs... originales.

Si bien la necesidad de emplear adaptadores como proj se ha visto aligerada en C++20 gracias a la integración de proyecciones en la implementación de una gran variedad de algoritmos estándar [4], esta herramienta puede resultar aún de gran utilidad a la hora de emplear vistas [5]. Consideremos, por ejemplo, el siguiente vector conteniendo los nombres y países de origen de algunos famosos pintores europeos:

   #include <string>    #include <vector>    #include <boost/hof/proj.hpp>    #include <fmt/format.h>    #include <range/v3/core.hpp>    #include <range/v3/action/sort.hpp>    #include <range/v3/algorithm/sort.hpp>    #include <range/v3/view/chunk_by.hpp>    struct Painter {       std::string name,    // nombre                    country; // país de origen    };    auto main() -> int    {       namespace bhf = boost::hof;       namespace rn = ranges;       namespace ra = rn::actions;       namespace rv = rn::views;       auto painters = std::vector<Painter>{          {.name = "Modigliani", .country = "Italy"},          {        "Cezanne",               "France"},          {        "Picasso",               "Spain"},          {        "Matisse",               "France"},          {        "Dali",                  "Spain"},          {        "Monet",                 "France"},       };

Nos proponemos mostrar dicha lista de pintores en la terminal, agrupados en base a sus países de procedencia. Para ello, reordenaremos el vector painters según el orden lexicográfico de país para, a continuación, emplear la vista ranges::views::chunk_by de la biblioteca Range-v3, la cual agrupará a los artistas asociados a una misma nación. Esta vista debe recibir como argumento un predicado binario --que nombraremos same_country-- que compruebe si dos entradas Painter del vector corresponden al mismo país (dato miembro country). Nos serviremos del adaptador boost::hof::proj para crear dicho predicado de forma directa:

      for (          auto same_country = bhf::proj(&Painter::country, rn::equal_to{});          auto country_grp : (painters |= ra::sort(rn::less{}, &Painter::country))                           | rv::chunk_by(same_country)       ) {          rn::sort(country_grp, {}, &Painter::name);          fmt::print("{:_^20}\n", country_grp.begin()->country);          for (Painter const& p : country_grp)             fmt::print("{:^20}\n", p.name);       }           } // fin de main()

La vista chunk_by, introducida en la rama master del proyecto Range-v3 [6] en septiembre de 2021, compara elementos consecutivos del rango e inicia un nuevo subgrupo cada vez que el predicado binario se evalúe como falso. Esto contrasta con el modo de funcionamiento de la vista group_by de dicha biblioteca, que siempre realiza la comparación del primer elemento de cada subrango con los elementos subsiguientes. Aun cuando en nuestro caso, basado en la equivalencia de elementos, ambas vistas producen el mismo resultado, conviene tener en cuenta que group_by será declarada obsoleta (deprecated) en el futuro [7]. Consúltese esta serie de artículos del blog para más detalles acerca de la biblioteca Range-v3.

De forma alternativa al uso del comparador equal_to, podrían emplearse los placeholders y operadores sobrecargados proporcionados en la cabecera <boost/hof/placeholders.hpp>. Así, incluyendo la cláusula using bhf::placeholders::_ en la función principal de nuestro código, podríamos escribir sencillamente:

   auto same_country = bhf::proj(&Painter::country, _ == _);

Por supuesto, de decidir no usar el adaptador proj, podríamos definir el predicado same_country a través de una expresión lambda de la forma (compárese la extensión del código con el anterior):

   auto same_country = [](Painter const& p1, Painter const& p2){                                                    return p1.country == p2.country; };

La ejecución del programa conduciría, en cualquier caso, a:

_______France_______ 

      Cezanne       

      Matisse       

       Monet        

_______Italy________

     Modigliani     

_______Spain________

Dali

Picasso

Indicar, por último, que de utilizar MinGW en MS Windows, sería necesario emplear el flag -fno-ms-extensions si deseamos que el adaptador proj admita punteros a funciones miembro públicas a modo de proyectores unarios [8].


Referencias bibliográficas
  1. Boost - HOF - https://www.boost.org/doc/libs/master/libs/hof/doc/html/doc/index.html
  2. Boost - HOF - proj - https://www.boost.org/doc/libs/master/libs/hof/doc/html/include/boost/hof/proj.html
  3. Boost - HOF - ConstInvocable concept - https://www.boost.org/doc/libs/master/libs/hof/doc/html/doc/src/concepts.html#constinvocable
  4. Jonathan Boccara - Fluent{C++} - 4 Features of Boost HOF That Will Make Your Code Simpler - https://www.fluentcpp.com/2021/01/15/4-features-of-boost-hof-that-will-make-your-code-simpler/
  5. Stack Overflow - Do C++ ranges support projections in views? - https://stackoverflow.com/questions/67618054/do-c-ranges-support-projections-in-views
  6. Range-v3 - https://github.com/ericniebler/range-v3
  7. Range-v3 - Pull request #1648 - https://github.com/ericniebler/range-v3/pull/1648
  8. GCC - Bug 94973 - https://gcc.gnu.org/bugzilla/show_bug.cgi?id=94973

No hay comentarios:

Publicar un comentario