Archive

Posts Tagged ‘C++ 11’

Functores y objetos llamables en la librería estándar de C++


Hace algunos días escribí acerca de los lambdas de C++ y C#. En esa oportunidad hice un recuento sobre las opciones que en C++ existen para tratar funciones como parámetros, o en general, poder tener “objetos llamables” como variables. En particular, exploramos tres alternativas:

1.- Funciones de retro-llamada. También llamados “callbacks”, estas funciones son los famosos punteros a void* (o al tipo de dato que retornan).

void print(const string& str)
{
    cout << str << endl;
}

void foo()
{
    void (*func)(const string& str);
    func = &print;
    func("Hola mundo!");
}

2.- Functores. Estas son clases que implementan el operador (), de tal suerte que simulan ser funciones, aunque en realidad sean clases.

class printer
{
    public:
        string str;

        printer() { };
        printer(const string& msg) : str(msg) { }

        void operator()() { 
            cout <str <str = msg;
            cout <str << endl;
        }
};

int main()
{
    printer func;
    func("Hola mundo!");

    func.str = "Hallo Welt!";
    func();

    return 0;
}

3.- Lambdas. Como comentamos, éstas son funciones anónimas cuyo cuerpo se define al momento, i.e. dentro de otra función.

auto func = [](const string& str) {
    cout << str << endl;
};

func("Hola mundo!");

Pues bien, hay una opción de la que no he hablado. En esencia no se diferencia mucho de un functor. O, mejor dicho, es un functor. O aún mejor dicho, es una plantilla que define un objeto que puede ser llamado, como una función o functor. ¡Argh! Me refiero a la clase function, definida en <functional>.

Esta clase es una plantilla cuyo parámetro es –escúchalo bien- la definición de una funcion. Es decir, su firma. Y obvio con esa firma es con la que se define el operador (). Por cierto, el operador de asignación está sobrecargado para recibir la función a ejecutar. Así es, a function de todas formas le tienes que pasar una función como parámetro. Veamos.

#include <iostream>
#include <functional>

using std::cout;
using std::endl;
using std::function;

int sqr(int a)
{
    return a * a;
}

int main(int argc, char* argv[])
{
    function f;
    f = sqr;
    cout << f(5) << endl;

    system("pause");
    return 0;
}

Obvio se imprime “25” en pantalla. En la primera línea definimos nuestra función con parámetro de plantilla int(int). Esto quiere decir que la función tendrá un parámetro de tipo entero y que regresa otro entero. La segunda línea asigna la función sqr (que cumple con la firma de la plantilla). La tercera línea simplemente invoca la función.

Si creas una función, no le asignas la referencia e intentas invocarla, te lanzará una excepción de tipo bad_function_call.

try
{
    function<int(int)> f;
    int a = f(5); // tsssss!
    cout << a << endl;
}
catch (const std::bad_function_call& ex)
{
    cout << ex.what() << endl;
}

Lo mejor: ¡también podemos usar lambdas!

typedef function<int(int, int)> bin_function;

bin_function suma;
suma = [](int a, int b) { return a + b; };

bin_function resta;
resta = [](int a, int b) { return a - b; };

cout << suma(31, 11) << endl;
cout << resta(67, 25) << endl;

Ahora bien, ¿qué ventaja tiene function<T> sobre usar lambdas? Pues que por un lado hace innecesario tener que crear nuestros propios functores de nuevo, y por otro lado, podemos usarlo como parámetro. Por ejemplo, para crear eventos o una implementación del patrón “observer”.

Veamos. Primero nos creamos una clase sencilla: empleado, la cual tiene tres miembros: para nombre, edad y domicilio. Privados. Luego creamos un par de métodos para cada una de éstas: uno que obtiene el dato y otro que lo escribe. Lo que queremos es que cada vez que alguna de estas tres propiedades se actualice, si el dato es diferente, entonces así lo notifique a quien quiera “escucharlo”. Al final, es es lo que hace el patrón de diseño observer. En fin, para ello creamos un método al cual se le llamará cuando cambien las propiedades. Algo así.

class employee
{
    public:
        employee() { }

        string name() const { return _name; }
        string address() const { return _address; }
        int age() const { return _age; }

        void name(const string& value) {
            if (value != _name) {
                _name = value;
                on_property_changed("name");
            }
        }

        void address(const string& value) {
            if (value != _address) {
                _address = value;
                on_property_changed("address");
            }
        }

        void age(int value) {
            if (value != _age) {
                _age = value;
                on_property_changed("age");
            }
        }
    protected:
        void on_property_changed(const string& property_name)
        {
        }

    private:
        string _name;
        string _address;
        int _age;
};

Pero ¿qué ponemos en on_property_changed? Lo que queremos es avisar que el objeto ha sido actualizado, así que esta función debería llamar a los “escuchas” o “listeners” pasándole una referencia a sí mismo. Luego, queremos pasar el nombre de la propiedad que ha cambiado. Para todo esto, quizás sea mejor crear una clase: la llamaremos event_args.

class event_args
{
    public:
        event_args(const employee& sender, const string& property_name)
            : _sender(sender), _property_name(property_name)
        {
        }

        const employee& sender() const { return _sender; }
        string property_name() const { return _property_name; }

    private:
        const employee& _sender;
        string _property_name;
};

Y ahora, nos basta un par de typedefs:  uno que defina una función (std::function<T>, recuerda) que tome como parámetro un objeto de tipo event_args, y luego, un contenedor de este tipo de funciones, como –digamos- std::list.

typedef function<void(event_args&)> event_function;
typedef list<event_function> event_listeners;

Ahora en la clase employee nos creamos un miembro (público, por facilidad) de tipo event_listeners…

event_listeners listeners;

…y procedemos a escribir, ahora sí, nuestra función on_property_changed, que recorra cada función de la lista y la invoque. Easy peasy.

void on_property_changed(const string& property_name)
{
    event_args args(*this, property_name);
            
    auto callback = [&args](event_function& func) { func(args); };
    for_each(listeners.begin(), listeners.end(), callback);
}

Bueno bueno, usamos un lambda y la función std::for_each, pero meh, es lo mismo. Y con esto ya tenemos listo nuestro patrón “observer”, y sólo resta el usarlo: primero creamos una función que imprime el nombre de la propiedad que ha sido modificada. Luego, hacemos otra función que haga ciertas validaciones (nombre no vacío, edad mayor de 16 años, etc.) e imprima un mensaje si no se cumplen. Luego instanciamos nuestro objeto employee y añadiremos las funcinoes a la propiedad listeners.

int main(int argc, char* argv[])
{
    auto print_func = [](event_args& args) { 
        cout << "La propiedad [" 
             << args.property_name()
             << "] ha cambiado. "
             << endl;
    };

    auto validate_func = [](event_args& args) {
        auto sender = args.sender();
        if (sender.name().length() <= 0)
            cout << "El nombre no puede estar vacío." << endl;
        if (sender.age() < 16)
            cout << "La edad mínima laboral es de 16 años." <= 255)
            cout << "La dirección es muy larga." << endl;
        cout << endl;
    };

    employee emp;
    emp.listeners.push_back(print_func);
    emp.listeners.push_back(validate_func);
    emp.name("Fernando");
    emp.name("");
    emp.name("Fernando Gómez");
    emp.age(29);
    emp.address("Algún lugar en la Ciudad de México.");

    system("pause");
    return 0;
}

Al ejecutarse, este programa tiene esta salida:

La propiedad [name] ha cambiado.
La edad mφnima laboral es de 16 a±os.

La propiedad [name] ha cambiado.
El nombre no puede estar vacφo.
La edad mφnima laboral es de 16 a±os.

La propiedad [name] ha cambiado.
La edad mφnima laboral es de 16 a±os.

La propiedad [age] ha cambiado.

La propiedad [address] ha cambiado.

Press any key to continue . . .

¡Así sí gana la gente!

Hemos visto en el pasado cómo usar diferentes técnicas para llamar funciones como variables o parámetros. Y ahora hemos visto cómo podemos combinarlas, junto con esta nueva función exclusiva de C++ 11, para lograr estructuras más complejas. No es que antes no pudieran hacerse, pero ahora no tenemos que escribir tanto código. Y al final de todo es es lo que debe proveer una buena librería, ¿o no?

Por último, vale la pena comentar que esto es apenas un vistazo a std::function y, sobre todo, a todos los objetos y funciones contenidos dentro del archivo <functional>. Poco a poco iremos explorando más posibilidades, así que ¡sigue en contacto!

¡Hasta la próxima!

Encuesta sobre Visual C++ 11, nuevas características y mis votos


En el blog del equipo Visual C++ ha aparecido una entrada en la que solicitan al mundo contestar una encuesta. Ésta hace referencia a aquellas características del nuevo estándar de C++ (llamado C++ 11) que queremos que el equipo de Visual C++ le de prioridad.

Como sabemos, algunas características de C++ 11 ya han sido incorporadas a Visual C++ 2010. Pero todavía faltan algunas otras, y Microsoft desea conocer nuestra opinión sobre a qué características darle prioridad.

Así que ya sabes: ¡participa! Aquí está la encuesta.

En mi caso, estas fueron las características que marqué como más urgentes.

1.- Delegating constructors. En C# podemos hacer esto:

class C
{
    string _param1;
    int _param2;

    C(string param1, int param2)
    {
        _param1 = param1;
        _param2 = param2;
    }

    C()
        : this(string.Empty, 0)
    {
    }
}

Esto rifa porque así creas un constructor genérico que puede ser invocado desde otros constructores. ¿No sería bueno contar con esto en nuestro Visual C++?

2.- Unicode literal strings. Para todos los que escribimos programas en otros lenguajes aparte del inglés, el mejorar el soporte para Unicode debería ser prioridad. Cierto, no sufrimos tanto como los chinos, árabes o hebreos, pero aún así debemos apoyar la causa: ¡por nuestro idioma!

3.- Use of char16_t y char32_t. Mismo razonamiento que el punto anterior, tratándose de tipos de dato para almacenar caracteres en Unicode (16 y 32 bits).

4.- Attributes. Similares a los atributos de C#:

// en C#
[WebMethod()] // este es un atributo
public void MyMethod()
{
}

El tener estos atributos en C++ nos ayudaría a olvidarnos de extensiones de compiladores como __declspec o __attribute__.

5.- Keyword exports. Aunque no es una opción, la puse en el punto 9, sobre comentarios adicionales.

La palabra reservada exports se utiliza cuando programamos clases plantillas. Hasta el momento, en Visual C++, tenemos que incorporar el cuerpo de nuestras funciones en la declaración de la clase. Intentarlo hacer por separado resulta en error de compilación.

Esta característica data del estándar C++ 98, si no me equivoco, pero Visual C++ nunca lo implementó. Y sé que no lo harán, menos ahora que el comité del estándar ha depreciado dicha palabra reservada (y la ha depreciado porque, salvo Comeau y Sun Studio, ningún otro compilador la implementó). Pero bueno, no se pierde nada con pedir.

Así que ya sabes, ¡vota por tus favoritas! ¡Participa!

Categorías:Noticias Etiquetas: ,

Comparando lambdas entre C++ y C#


Ahora que me ha dado por explorar las nuevas características de C++ 11, muchas de las cuales han sido ya incorporadas a Visual C++ 10, entre otras cosas me he puesto a explorar los lambdas de C++. Y naturalmente, tras mucho tiempo de trabajar con C# y .NET surge la inquietud/curiosidad de compararlas. Así, comencemos a revisar un poco de historia.

Funciones de retro-llamada (callbacks)

En la era antigua, cuando un programador de C++ quería pasar una función como parámetro, sacaba su cincel y tallaba en piedra un código similar al que sigue:



#include <string>
#include <iostream>
#include <sstream>
#include <algorithm>
#include <list>

using namespace std;

class employee
{
    public:
        employee()
            : _age(0), notify_changes(nullptr)
        {
        }

        employee(const string& name, int age, double salary)
            : _name(name), _age(0), notify_changes(nullptr)
        {
        }

        employee(const employee& copy)
            : _name(copy._name), _age(copy._age), notify_changes(nullptr)
        {
        }

        string name() const { return _name; }
        int age() const { return _age; }
        
        void (*notify_changes)(const string& msg);

        void name(const string& value)
        {
            if (_name != value)
            {
                _name = value;
                if (notify_changes != nullptr)
                {
                    string msg = "Name ha cambiado, nuevo valor: " + value;
                    notify_changes(msg);
                }
            }
        }

        void age(int value)
        {
            if (_age != value)
            {
                _age = value;
                if (notify_changes != nullptr)
                {
                    stringstream msg;
                    msg << "Age ha cambiado, nuevo valor: " << value;
                    notify_changes(msg.str());
                }
            }
        }

    private:
        string _name;
        int _age;
};

void print(const string& msg)
{
    cout << "\nRecibiendo mensaje: " << "\n\t" << msg << endl;
}

int main(int argc, char* argv[])
{
    employee emp;
    emp.notify_changes = &print;

    emp.name("Fernando Gómez. ");
    emp.age(28);

    system("pause");
    return 0;
}

Este código muestra un básico patrón del observador, en el cual una entidad externa al objeto observado recibe “notificaciones” o actualizaciones por parte de éste. En este caso tenemos una clase employee que “notifica” cuando alguna de sus propiedades cambia. Para la notificación se expone un puntero a función (también llamados callbacks o, intentando traducir, función de retro-llamada), llamado notify_changes. Pueden ver la sintaxis que es un poco engorrosa. Así, cuando name o age cambian, invocamos el puntero a la función, si éste no es nulo.

Podemos ver que en main(), es simple cuestión de asignar al puntero una función, en este caso una referencia a print. Tras esto, cualquier invocación a name() o age() hará que print() sea ejecutada.

image

Así pues, esta es la forma en la que hacíamos las cosas con C++ hace años. Por cierto que podemos usar plantillas para que la cosa no quede tan fea. Así fue como muchos elementos de la biblioteca estándar de C++ fueron implementados.

Functores

Como puedes ver del ejemplo anterior, los callbacks proveen ciertas dificultades. De entrada la sintaxis, pero además la falta de poder usar polimorfismo y otras herramientas del lenguaje.

Ante esto, comenzó a desarrollarse un idioma en C++, el cual consiste en sobrecargar el operador () para tratar clases como si fueran funciones. A este tipo de construcción se le llamó un functor. Y son perfectos para utilizarse con plantillas.

class functor
{
    public:
        int a; int b;

        void operator(const string& msg) const
        {
            cout << msg << a + b << endl;
        }
};

...

functor func;
func.a = 5;
func.b = 10;
func("Resultado de suma: ");

El código anterior muestra un functor básico. Dado que es una clase podemos hacer todo lo que hacemos con clases: tener estado, otros métodos y propiedades, atributos, herencia, polimorfismo… Mucho mejor que un simple callback.

Eventos y delegados en .NET

Cuando llega .NET una de las cosas que más recuerdo era la posibilidad de crear callbacks de forma fácil, fuertemente tipados y siendo menos propensos a errores. Esta característica de C# fue la que más llamó mi atención. Aunque, por supuesto, en .NET no se llaman callbacks, sino delegados.

public delegate void NotifyChanges(string msg);
...

public void Print(string msg)
{
    Console.WriteLine("Recibiendo mensaje: " + msg);
}
...

NotifyChanges miCallback = new NotifyChanges(Print);
miCallback("¡Hola mundo!");

Del código anterior la primera línea declara un delegado. Es similar a la declaración de un callback de C++, pero quitando la sintaxis horrible. Luego mostramos un método cualquiera, similar al print anterior, y por último mostramos cómo incializar una variable delegado y hacer que apunte hacia el método Print anterior.

Los eventos son semánticamente diferentes a los delegados, pero técnicamente son prácticamente lo mismo. Para declarar un evento (dentro de una clase o interfaz) necesitamos usar la palabra reservada event. Por lo demás, los invocamos como delegados. Así, podemos replicar el código del empleado anterior en C#.

using System;

public delegate void NotifyFunc(string msg);

public class Employee
{
    public Employee(string name, int age)        
    {
        _name = name;
        _age = age;
    }

    public Employee()
        : this(string.Empty, 0)
    {
    }

    public event NotifyFunc Notify;

    protected virtual void OnNotify(string msg)
    {
        if (Notify != null)
            Notify(msg);
    }

    public string Name
    {
        get { return _name; }
        set
        {
            if (_name != value)
            {
                _name = value;
                OnNotify("Name ha cambiado, nuevo valor: " + value);
            }
        }
    }

    public int Age
    {
        get { return _age; }
        set
        {
            if (_age != value)
            {
                _age = value;
                OnNotify("Age ha cambiado, nuevo valor: " + value);
            }
        }
    }

    private string _name;
    private int _age;
}

static class Program
{
    static void Print(string msg)
    {
        Console.WriteLine("Recibiendo mensaje: " + msg);
    }

    static void Main(string[] args)
    {
        Employee emp = new Employee();
        emp.Notify += Print;
        emp.Name = "Fernando Gómez. ";
        emp.Age = 28;

        Console.ReadKey(true);
    }
}

La utilización de delegados se ha extendido mucho en el mundo de C# y .NET. Las interfases gráficas es quizás donde más se emplean. Pero también otros lados. Por ejemplo, la clase List<T> tiene un método Find, que toma un predicado como parámetro, un delegado que regresa true o false, dependiendo de si es el elemento buscado.


bool FindByName(Employe emp)
{
    return emp.Name == "Fernando";
}

List list = new List();
...
Predicate func = new Predicate(FindByName);
Employee found = list.Find(func);

 

Ahora bien, desde hace alguans versiones de .NET exsiten los delegados anónimos. Éstos nos permiten declarar funciones dentro del cuerpo de otro método. Así, el ejemplo anterior podríamos reescribirlo como:

List<Employee> list = new List<Employee>();
...

Employee found = list.Find(new delegate(Employee emp) {
        return emp.Name == "Fernando";
    });

 

Lambdas en .NET

Como hemos podido ver, los delegados ayudan en muchas situaciones, como en la creación de eventos, la implementación de ciertos patrones de diseño y como en situaciones como el List<T>.Find. Sin embargo, la sintaxis no es muy cómoda aun.

Del cálculo lambda, aquella rama de las matemáticas que se dedica a investigar la definición de función, aplicación de funciones y recursión, surge el concepto de funciones lambda. Éstas son, en esencia, declaración de funciones anónimas (en .NET delegados anónimos) utilizando una sintaxis similar. Una función, por ejemplo, indica su parámetro seguido de una flecha, donde se indica el resultado. Así, f(x) = x*x podemos reescribirla como: x –> x*x usando dicha notación.

Pues bien, en C# así se implementan. La siguiente declaración crea un delegado anónimo que toma un número como parámetro y regresa su cuadrado.

Func<int, int> func = x => x * x;

Por supuesto, podemos usar un lambda para crear un delegado que tome como parámetro un objeto de tipo Employee y nos regrese un valor booleano:

var func = x => x.Name = "Fernando";

Ahm… bueno, la verdad es que el código anterior no funciona… Con una declaración así, el compilador de C# no puede saber el tipo de la variable x. Pero podemos cambiarlo de tal  forma que sí lo infiera. Por ejemplo, un delegado de tipo Predicate concuerda con la definición de lo que queremos hacer.

Predicate<Employee> func = x => x.Name == "Fernando";

De esta forma el compilador infiere el tipo del delegado. Funciona igual con los parámetros de un método: gracias a esto podemos usar un lambda directamente en nuestro método Find:

List<Employee> list = new List<Employee>();
...

Employee found = list.Find(x => x.Name == "Fernando");

Mucho mejor, ¿no?

Ahora bien, ¿qué pasa si necesitamos más parámetros o ejecutar más acciones? Pues usamos paréntesis y llaves. Por ejemplo, el método List<T>.Sort pide un delegado que tome dos parámetros de entrada y los compare.

List<Employee> list = new List<Employee>();
...

list.Sort( (x, y) => x.Name.CompareTo(y.Name));

Si quisiéramos usar un lambda que además escriba la comparación en la consola, haríamos algo así:

List<Employee> list = new List<Employee>();
...

list.Sort( (x, y) => {
    Console.WriteLine("Comparando: {0} vs {1}", x.Name, y.Name);
    return x.Name.CompareTo(y.Name);
});

Hay otra característica de las funciones lambdas que no hemos mencionado: la capacidad para acceder a variables que están al alcance. Por ejemplo, regresemos al ejemplo de la búsqueda. Quizás en lugar de comparar contra “Fernando” queremos comparar contra el valor de alguna variable. En ese caso, es legal hacer esto:

List<Employee> list = new List<Employee>();
...

string name = "Fernando";
Employee found = list.Find(x => x.Name == name);

La función lamda está accediendo a una variable que está declarada fuera de su cuerpo. Por supuesto, el compilador CLR, internamente, al crear el delegado anónimo, detecta que la variable externa es utilizada dentro del lambda y genera una función con un parámetro adicional. Dicha variable externa siempre se pasará por referencia. Ésto último podemos probarlo de forma sencilla:

Int32 i = 0;
Action func = () => i++;
func();
Console.WriteLine(i);

Cuando ejecutamos este código, se imprime “1”. Action es un delegado sin parámetros y con tipo de retorno void. Creamos un lambda: () indica que no hay parámetros, y simplemente aumentamos en uno la variable externa. E Int32 es una estructura, que se pasaría por valor en cualquier función normal. No en este caso.

Lambdas en C++

Así, ahora que estamos al borde de la versión 4.5 de .NET, vemos cómo los lambdas de .NET han simplificado el desarrollo, y son en esencia azúcar sintáctica bastante útil. Pues bien, dada su popoularidad (no sólo en .NET, sino también en el mundo de la programación funcional, en lenguajes como OCaml o incluso el reciente F#), hace tiempo que el comité que aprueba el estándar de C++ ha trabajado para incorporar lambdas a este lenguaje. Finalmente se han aceptado y ya están definidos en la versión del estándar C++ de 2011.

Afortunadamente para quienes usamos herramientas Microsoft, nuestro querido y no siempre bien ponderado Visual C++ 2010 ¡ya incluye esta característica! Así que ya podemos usar lambdas en C++. Aunque a fuer de ser sincero, los lambdas de C++ tienen una sintaxis… ehm… compleja, digamos.

Vamos por partes. Para declarar un lambda comenzamos por poner unos corchetes. Acto seguido, entre paréntesis listamos los parámetros como los de cualquier función. Luego, entre llaves, va el cuerpo del método. Algunos ejemplos.

auto func1 = []() { };
func1();

auto func2 = []() { cout << "Hola mundo!" << endl; };
func2(); // imprime el Hola mundo!

auto func3 = [](int i) { return i * i; };
int sqr = func3(5); // regresa 25
cout << sqr << endl;

auto func4 = [](list<int>& list) {
    for (auto i = list.begin(); i != list.end(); ++i)
        cout << *i << endl;
};

auto func5 = [](list<int>& list) {
    for (int i = 1; i <= 10; i++)
        list.push_back(i*i);
};
list<int> l;
func5(l);
func4(l);

En este ejemplo creamos cinco funciones lambdas. Nos apoyamos en la palabra reservada “auto” para determinar el tipo de variable con base en el tipo de la asignación (igual al “var” de C#).

La primera función, func1, no hace nada: no tiene parámetros, no tiene valor de retorno ni haca absolutamente nada: posiblemente el lambda más sencillo que podamos crear. Por su parte, func2 imprime en consola el tradicional “Hola mundo!”. Esta lambda no tiene parámetros ni tipo de retorno (es decir, es “void”), pero sí tiene cuerpo. Con func3 la cosa ya se pone más interesante: toma un parámetro y regresa el cuadrado del mismo.

La lambda func4 está más interesante aún: toma una referencia a una lisat e imprime en consola todos sus elementos. Adicionalmente, func5 toma una lista también y le inserta el cuadrado de los primeros diez números naturales. Luego nos creamos una lista y la pasamos a func5 para que genere los números y luego a func4 para que los imprima.

Ahora bien, te preguntarás: ¿por qué no mejor creamos la lista y que el lambda haga referencia a ésta, sin tener que pasarla como parámetro? ¡Buena idea!

list<int> l;
auto func = [](int min, int max) {
    for (int i = min; i < max; i++)
        l.push_back(i*i);
};
func(1, 10);

Similar a C#, ¿no? Sin embargo, al compilar… ¡tómala! Aparece este mensaje de error:

'l' cannot be implicitly captured because no default capture mode has 
been specified.

Changos. ¿Por qué no compila? Cuando veíamos la versión de C# dijimos que el compilador del CLR pasa en automático las variables que están fuera del lambda. En C++ no se hace en automático: hay que especificarle al compilador cómo queremos pasar las variables externas. Y (por si te lo habías preguntado) aquí es donde entran en juego los corchetes.

En efecto, dentro de los corchetes especificamos qué parámetros externos usaremos, y cómo queremos pasarlos a la función lambda. Hasta ahora hemos dejado corchetes vacíos, que quiere decir: no pases ninguna variable externa. Ahora lo que haremos será añadir un ámperson adentro, lo cual quiere decir: pasa todas las variables por referencia.

list<int> l;
auto func = [&](int min, int max) {
    for (int i = min; i < max; i++)
        l.push_back(i*i);
};
func(1, 10);

Como puedes ver en la línea resaltada, hemos añadido el ámperson. Así, estamos pasando todas las variables externas por referencia, y por ende “l” se pasa así. Ya no tenemos problema.

Ahora bien, supongamos que cambiamos la definición del lambda y tanto min como max serán variables externas. Pero éstas no queremos pasarlas por referencia, sino por valor. Lo que hacemos es enumerar entre los corchetes la lista de parámetros externos, separados por coma, y poner el nombre de la variable en cuestión.

list<int> l;
int min = 1;
int max = 10;
auto func = [&l, min, max]() {
    for (int i = min; i < max; i++)
        l.push_back(i*i);
};
func();

Como puedes ver en el ejemplo, estamos pasando la lista por referencia, mientras que el rango min-max lo pasamos por valor.

Por último, hay que recordar que los lambdas generan un tipo de dato bien definido. Por ello, podemos usarlos incluso como parámetros de plantillas. De esto mismo ya hemos hablado en una entrada anterior.

Comparando

A lo largo de esta entrada hemos visto cómo históricamente ha sido necesario tratar las funciones como si fueran variables. Al principio teníamos callbacks, y luego en el mundo de .NET tuvimos delegados. Luego vimos cómo éstos evolucionaron en delegados anónimos y finalmente en lambdas. Y por último, vimos cómo C++ se puso al corriente en este tema.

Tras este recorrido, me gustaría señalar algunas similitudes y diferencias que he encontrado entre ambas implementaciones. Sería interesante que si conoces alguna que no esté listada, ¡me la hagas llegar para exponerla y platicarla!

0.- La sintaxis de C++ apesta y la de C# es más limpia.

Bueno, al menos con C# podemos usar expresiones compactas cuando necesitamos una sóla línea de código, como al momento de filtrar o comparar en los ejemplos anteriores. Con C++ nunca es el caso. Pero bueno, al final no debería sorprendernos: la sintaxis de C++ suele ser más complicada aunque en ocasiones menos verbosa. C# por su parte fue diseñado para ser un lenguaje much más claro.

1.- Los lambdas en C# siempre se traducen a un delegado, mientras que los lambdas en C++ se traducen a un tipo de dato indefinido.

Esto, por supuesto, no quiere decir que en C++ los lambdas no tengan tipo: claro que lo tienen, tanto que hasta podemos usar typeid de esta forma:

auto func = []() { };
const type_info& type = typeid(func);
cout << type.raw_name() << endl;

/* en mi compilador, lo anterior imprime:

.?AV@?A0xe4d096c9@@
Press any key to continue . . .

*/

La diferencia radica en que en C++ nunca sabremos bien el tipo de dato del lambda, mientras que en C# sí lo sabemos. De hecho, en C# el compilador necesita alguna forma de inferir el tipo de dato, es decir, necesita inferir el delegado en cuestión.

2.- En C++ los lambdas pueden asignarse a variables con tipo de dato implícito, mientras que en C# no puede hacerse.

Esto es un corolario del punto anterior. En efecto, mientras que en C++ podemos inicializar un lambda hacia una variable con el modificador “auto”, hacer algo similar en C# con la palabra “var” nos manda error de compilación.

// C++ - OK
auto func = []() { };
// C# - error CS0815 Cannot assign lambda 
// expression to an implicitly-typed local variable
var func = () => { };

Como decíamos: en C++, el lambda tiene un tipo anónimo y por eso “auto” funciona, mientras que en C# necesariamente tiene que mapearse a un delegado: es por ello que el lambda necesita conocer de antemano el tipo del delegado.

3.- En C# todas las variables externas están disponibles en automático, y en C++ hay que hacerlo explícito.

En efecto, hemos visto cómo en C# podemos referenciar cualquier variable externa y listo, mientras que en C++ tenemos que, o bien nombrarlas explícitamente, o bien especificar que queremos que todo se pase por referencia (con el “[&]”).

4.- En C++ podemos controlar cómo pasar las variables externas.

Como ya decíamos, podemos especificar si una variable externa se pasa por valor o por referencia, o incluso pasar una por valor y otra por referencia.

5.- En ambos casos, las librerías de ambos lenguajes permiten usar lambdas para realizar filtros y acciones sobre datos.

Vimos varios ejemplos, como List<T>.Find o List<T>.Sort. Por el lado de C++, podemos usar std::for_each para ejecutar una acción (el lambda) sobre algún iterador. Por ejemplo:

list<int> l;
for (int i = 1; i <= 10; i++)
	l.push_back(i);

for_each(l.begin(), l.end(), [](int i) { cout << i*i << endl; });

 

Por último…

Bueno, pues eso ha sido todo por hoy. Estaría interesante que pudiésemos seguir analizando los lambdas, así que si quieres comentar algo, no dudes en dejar tu mensaje. ¡Incluso, me gustaría que buscásemos más similitudes y diferencias que las aquí mencionadas!

¡Nos vemos hasta la próxima!

Categorías:.NET Framework, Apunte, C#, C++ Etiquetas: , , ,