STL Algorithms: Iterando com std::for_each

por Fabio A. Mazzarino

C++ tem várias opções de iteração. Desde o C++98 existe o algoritmo for_each disponível. Basicamente o que este algoritmo faz é executar uma função a todos os elementos contidos em um tipo iterável, tal como um std::vector, ou um std::map, dentre diversos outros.

Simplesmente Utilizando

No exemplo abaixo exploramos duas possibilidades do for_each. A primeira é aplicar uma função unária a cada elemento. A segunda é aplicar um classe que sobrecarregue o operator () (na verdade tem que ser uma CopyConstructor ou MoveConstructor, mas vamos deixar mais simples por enquanto).

#include <algorithm>
#include <iostream>
#include <list>

class sumop {
    protected:
        int total;
    public:
        sumop(int start = 0) { total = start; }
        void operator() (int i) {
            total += i;
            std::cout << total << " ";
        }
        int getTotal() { return total; }
};

void coutfn(int i) {
    std::cout << i << " ";
}

int main() {
    std::list<int> l;
    sumop sum(0);

    l.puh_back(11);
    l.push_back(12);
    l.push_back(13);
    
    std::cout << "l: ";
    std::for_each(l.begin(), l.end(), coutfn);
    std::cout << std::endl;

    std::for_each(l.begin(), l.end(), sum);
    std::cout << std::endl;
    std::cout << "sum l: " << sum.getTotal() << std::endl;
}

No primeiro for_each aplicamos uma função que imprime cada membro do std::list. No segundo somamos cada membro do std::list, mas a saída do programa é a seguinte:

l: 11 12 13
11 23 36
sum l: 0

Note que a soma não funcionou. Vamos consultar a documentação.

O que Deu Errado?

Note na documentação que a declaração do for_each as classes CopyConstructor e MoveConstructor são passadas por valor, e não por referência. Por consequência a instância de sumop que efetua a somatória é diferente da instância sumop que é declarada dentro do main().

Uma opção para resolver esse problema, sem utilizar outro algoritmo é utilizar uma variável estática. Assim:

#include <algorithm>
#include <iostream>
#include <list>

class sumop {
    protected:
        static int total;
    public:
        static void reset() {
            total = 0;
        }
        void operator() (int i) {
            total += i;
            std::cout << sumop::total << " ";
        }
        static int getTotal() {
            return total;
        }
};

int sumop::total = 0;

void coutfn(int i) {
    std::cout << i << " ";
}

int main() {
    std::list<int> l;
    sumop sum;

    l.push_back(11);
    l.push_back(12);
    l.push_back(13);

    std::cout << "l: ";
    std::for_each(l.begin(), l.end(), coutfn);
    std::cout << std::endl;

    sumop::reset();
    std::for_each(l.begin(), l.end(), sum);
    std::cout << std::endl;
    std::cout << "sum l: " << sumop::getTotal() << std::endl;
}

E a saída apropriadamente:

l: 11 12 13
11 23 36
sum l: 36

As possibilidades do for_each são muito grandes, mas é necessário prestar atenção não somente na documentação do for_each, mas de qualquer outra função, método, ou API para evitar falhas desse tipo.