Última actualización: 30 de agosto de 2020.
Artículos de la serie:
- Objectos función (function objects).
- Objetos función con estado interno.
- Expresiones lambda: sintaxis y uso combinado con algoritmos.
- Invocación inmediata de lambdas (técnica IIFE).
Objectos función (function objects)
Antes de poder entender qué son y cómo funcionan las expresiones lambda, es preciso que comprendamos el concepto de objeto función.
En el lenguaje C++ las clases pueden sobrecargar el operador llamada a función operator() con el fin de dotar a sus objetos del comportamiento propio de las funciones tradicionales. A modo de ejemplo, consideremos la plantilla estándar std::greater<> proporcionada en el fichero de cabecera <functional>, destinada a realizar la comparación de dos valores dados a través del operador "mayor estricto que" operator>. Una posible implementación de esta plantilla en C++20 vendría dada por:
namespace std {
template<typename T>
struct greater {
constexpr auto operator()(T const& x, T const& y) const -> bool { return x > y; }
};
}
Dado un objeto de la clase anterior auto g = std::greater<int>{} y dos valores cualesquiera a y b convertibles a tipo int, la expresión g(a,b) será interpretada como una llamada explícita al operator operator(), resolviéndose como g.operator()(a,b).
Obviamente, el booleano retornado será verdadero o falso en función del orden de los argumentos:
using namespace std;
auto g = greater<int>{};
cout << boolalpha << g(3,4) << '\n' // equivalente a g.operator()(3,4); output: false
<< g(4,3); // equivalente a g.operator()(4,3); output: true
Con carácter general, el estándar del lenguaje define como tipo de objeto función (function object type) a todo aquél que pueda ser utilizado a la izquierda del operador llamada a función
postfix-expression(expression-listopt).
Entre estos tipos, cabe distinguir:
- Punteros a funciones.
- Clases con sobrecargas del operador operator() como función miembro pública. Estas clases son tradicionalmente conocidas con el sobrenombre inglés de functors.
- Clases convertibles a punteros a función.
Observemos que una función tradicional no constituye un objeto función, pero puede emplearse allá donde se espere un objeto función gracias a su conversión implícita en puntero a función.
Los algoritmos de la biblioteca estándar que requieran el uso de funciones externas están diseñados para aceptar objetos función por valor, y no únicamente punteros a función como sería natural en un código en C. Tal es el caso del algoritmo de ordenación std::sort():
Aquí, comp es un objeto función (en este caso, un predicado binario) que establece la política de ordenación (orden débil estricto) a seguir. Por ejemplo, este algoritmo, en conjunción con la estructura std::greater<> descrita anteriormente, permite ordenar un vector de enteros por orden estrictamente decreciente de elementos. En el siguiente código, un objeto temporal std::greater<int> es inicializado directamente como tercer argumento de la función std::sort:
De forma conveniente, podemos servirnos de la funcionalidad CTAD de C++17 (Class Template Argument Deduction) para deducir de forma automática tanto el tipo de elemento contenido en el vector como el tipo de elemento a ordenar:
El lector quizás se pregunte las ventajas que puede obtener un programador al trabajar con clases functors frente a las funciones tradicionales. Si bien comentaremos importantes funcionalidades en próximos posts, citaremos aquí un aspecto esencial relacionado con la eficiencia del código: Al trabajar con functors, los compiladores han sido tradicionalmente capaces de reemplazar con facilidad las llamadas a la función miembro operator() por el cuerpo del operador en el código fuente. Ello incrementa notablemente la eficiencia de algoritmos como std::sort() al evitar el coste de repetidas llamadas al operador. Esto contrasta vivamente con el uso de punteros a función, cuya sustitución en línea (inline) depende críticamente de las optimizaciones realizables por el compilador, así como de la estructura/complejidad de la función en cuestión (véase el tutorial proporcionado por MSDN Channel 9, citado en las referencias bibliográficas, para más detalles).
template<typename RandomIt, typename Compare>
constexpr auto sort(RandomIt first, RandomIt last, Compare comp) -> void;
Aquí, comp es un objeto función (en este caso, un predicado binario) que establece la política de ordenación (orden débil estricto) a seguir. Por ejemplo, este algoritmo, en conjunción con la estructura std::greater<> descrita anteriormente, permite ordenar un vector de enteros por orden estrictamente decreciente de elementos. En el siguiente código, un objeto temporal std::greater<int> es inicializado directamente como tercer argumento de la función std::sort:
auto v = std::vector<int>{1, 7, -2, 0, 5};
std::sort(v.begin(), v.end(), std::greater<int>{}); // v es ordenado a {7,5,1,0,-2}
De forma conveniente, podemos servirnos de la funcionalidad CTAD de C++17 (Class Template Argument Deduction) para deducir de forma automática tanto el tipo de elemento contenido en el vector como el tipo de elemento a ordenar:
auto v = std::vector{1, 7, -2, 0, 5}; // v es de tipo std::vector<int>
std::sort(v.begin(), v.end(), std::greater{}); // ordenación de enteros
El lector quizás se pregunte las ventajas que puede obtener un programador al trabajar con clases functors frente a las funciones tradicionales. Si bien comentaremos importantes funcionalidades en próximos posts, citaremos aquí un aspecto esencial relacionado con la eficiencia del código: Al trabajar con functors, los compiladores han sido tradicionalmente capaces de reemplazar con facilidad las llamadas a la función miembro operator() por el cuerpo del operador en el código fuente. Ello incrementa notablemente la eficiencia de algoritmos como std::sort() al evitar el coste de repetidas llamadas al operador. Esto contrasta vivamente con el uso de punteros a función, cuya sustitución en línea (inline) depende críticamente de las optimizaciones realizables por el compilador, así como de la estructura/complejidad de la función en cuestión (véase el tutorial proporcionado por MSDN Channel 9, citado en las referencias bibliográficas, para más detalles).
Referencias bibliográficas:
- Stroustrup B., 'The C++ Programming Language'. Addison-Wesley, 4th Edition (2013).
- https://eel.is/c++draft/function.objects
- MSDN Channel 9 - Stephan T. Lavavej - https://channel9.msdn.com/Series/C9-Lectures-Stephan-T-Lavavej-Standard-Template-Library-STL-/C9-Lectures-Stephan-T-Lavavej-Standard-Template-Library-STL-6-of-n
No hay comentarios:
Publicar un comentario