28 de Julio de 2010

A menudo se dice que C++ es un lenguaje complicado, y como ejemplo se cita la iteración sobre los elementos de un contenedor (una lista, por ejemplo): otros lenguajes tienen una sintaxis de tipo for-each, pero en C++ hay que hacer un bucle manual, con iteradores e incremento. Pero ¡ya no más! C++0x incluirá una nueva sintaxis de la sentencia for que será la envidia de los usuarios de otros lenguajes. Primero veamos un ejemplo de cómo se hace una iteración sobre un contenedor en C++ actual:

typedef std::vector<Dato> cont_t;
cont_t datos;
//...
for (cont_t::iterator it = datos.begin(); it != datos.end(); ++it)
{
    Dato &dato = *it;
    //...
}

No es difícil de entender ni de escribir, pero es innecesariamente verboso, sobre todo si no tenemos el typedef. Otros lenguajes, como Java o C# tienen sintaxis especiales para iterar sobre contenedores:

//En Java
List<Dato> datos;
//...
for (Dato dato : datos)
{
    //...
}

¡Mucho más bonito! Naturalmente el contenedor debe implementar la interfaz Iterable<Dato> para que funcione.

El futuro

En C++0x, la sintaxis para crear un bucle for-each será:

std::vector<Dato> datos;
//...
for (Dato &dato : datos)
{
    //...
}

No está mal, ¿eh? Y fíjate que ya no necesitamos el typedef para nada. Naturalmente, si te sientes especialmente perezoso puedes combinarlo con [auto](/es/tipos-automaticos-con-auto/) (la referencia es opcional, claro):

std::vector<Dato> datos;
//...
for (auto &dato : datos)
{
    //...
}

¿Y cuáles son los requisitos del tipo de la expresión a la derecha del :? Hay dos opciones:

  • Es un array, en cuyo caso el código funciona de forma natural.
  • No es un array, entonces las expresiones begin(datos) y end(datos) deben tener sentido y devolver valores del mismo tipo. Este tipo se usará como iterador interno en el bucle, por lo que debe tener sobrecargados los operadores de auto-incremento (++ prefijo), de referencia (*) y de desigualdad (!=).

Como ayuda natural, la librería estándar define las funciones template:

namespace std
{

template<typename T> auto begin(T &x) -> decltype(x.begin())
{ return x.begin(); }
template<typename T> auto begin(T &&x) -> decltype(x.begin())
{ return x.begin(); }

template<typename T> auto end(T &x) -> decltype(x.end())
{ return x.end(); }
template<typename T> auto end(T &&x) -> decltype(x.end())
{ return x.end(); }

}

NOTA: Si te sorprende la sintaxis peculiar de estas funciones, ya la he comentado en una ocasión.

Con estas funciones template, el bucle for-each itera entre los iteradores devueltos por las funciones miembro begin() y end() que poseen todos los contenedores de la librería estándar.

Pero... ¡un momento! Estas funciones existen en el espacio de nombres std, mientras que antes escribí simplemente begin(datos), sin añadir std::. Pues resulta que es correcto, porque cuando en C++ se llama a una función sin indicar ningún espacio de nombres se busca la función utilizando la búsqueda de dependiente de argumentos (argument dependent lookup) o búsqueda Koenig (en honor del primero en proponerla). Esta consiste en que la función se busca en todos los espacios de nombres en los que reside el tipo de alguno de los parámetros de la función. Así, en begin(datos), siendo datos de tipo std::vector<Dato>, la función begin se busca en el espacio de nombres std.

¿Y qué pasa si escribimos nuestros propios tipos iterables? Lógicamente este tipo nuestro no va a residir en std. Aquí los diseñadores del lenguaje se tomaron una cierta libertad y decidieron que las funciones begin() y end() se deben buscar en std siempre, aun cuando el parámetro no tenga nada que ver con ese espacio de nombres. Por lo tanto, para hacer nuestro tipo T iterable tenemos dos opciones:

  • Añadir las funciones miembro T::begin() y T::end(), que devuelvan un puntero, un iterador o un tipo con sobrecargas en operator++(), operator!=() y operator*(). Es necesario incluir <iterator>.
  • Definir dos funciones globales begin(T &) y end(T &), que devuelvan lo mismo que las anteriores. Estas funciones deben residir en el mismo espacio de nombres que el tipo T. Esta solución es ideal si el tipo T no lo creamos nosotros y no podemos modificarlo.

No tenemos por qué limitarnos a contenedores, podemos echarle imaginación. El siguiente ejemplo imita la función xrange de Python:

#include <iterator>
#include <iostream>

class rango
{
public:
    rango(int n)
    :m_num(n)
    {}

    class iterator
    {
    public:
        int operator *() const
        {
            return m_x;
        }
        iterator &operator ++()
        {
            return m_x;
        }
        bool operator != (const iterator &i) const
        {
            return m_x != i.m_x;
        }
    private:
        iterator(int x)
        :m_x(x)
        {}
        int m_x;
    };
    iterator begin() const
    {
        return iterator(0);
    }
    iterator end() const
    {
        return iterator(m_num);
    }
private:
    int m_num;
};

int main()
{
    for (int n : rango(10))
        std::cout << n << std::endl;
    return 0;
}

Por último, la librería estándar incluye un par de sobrecargas de begin() y end() interesantes:

namspace std
{
template<typename IT> IT begin(const pair<IT, IT> &p)
{
    return p.first;
}
template<typename IT> IT end(const pair<IT, IT> &p)
{
    return p.second;
}
}

No es para iterar sobre un std::pair, sino para utilizar un std::pair como par de iteradores que definen el rango que se va a recorrer. La idea es que una función puede devolver uno de estos pares, y se puede hacer el bucle for directamente. La librería estándar, que yo sepa, solo define funciones de este tipo en los contenedores asociativos (std::map, std::multimap, etc.). Por ejemplo:

std::multimap<int, std::string> dict;
//...
//Vamos a iterar todos los elementos del 
//dict con clave '3'
//'auto' es por pereza, porque sería
//std::multimap<int, std::string>::value_type
for (const auto &kv : dict.equal_range(3))
{
    const std::string &val = kv.second;
    //...
}

0 comentarios a Bucle foreach en C++0x

Ayuda
:-(icon_sad :-)icon_smile :roll:icon_rolleyes
:-Dicon_biggrin :-Picon_razz :oops:icon_redface
:-xicon_mad :-|icon_neutral :arrow:icon_arrow
8-)icon_cool 8-Oicon_eek :mrgreen:icon_mrgreen
:!:icon_exclaim :?:icon_question :twisted:icon_twisted
;-)icon_wink :evil:icon_evil :idea:icon_idea
:-oicon_surprised :cry:icon_cry
:-?icon_confused :lol:icon_lol

Deja un comentario