Generators 2 в избранное  новое всё   подписка   модер. 
От: remarkhttp://www.1024cores.net/
Дата: 28.05.08 09:36
Оценка:39 (7)
Немного пропатчил версию c-smile:
http://www.rsdn.ru/forum/message/2965247.1.aspx
Автор: c-smile
Дата: 27.05.08


Дабы пофиксить следующие вещи, которые всплыли при первой же попытке реально применить Generators:

1. Почему "do {...} while (gen);", а не "while (gen) {...};"? А если последовательность пустая?!

2. Попытка вызвать в теле цикла gen() несколько раз приводит в очень неинтуитивному результату. Хотелось бы что бы новый элемент получался только после прохождения итерации, т.е. после вызова gen.operator bool().

3. Необходимость передавать в макрос stop() реальный элемент. Очень не удобно. Очень. Выход из функции должен означать конец последовательности, никаких элементов уже не должно быть.


И ещё добавил, что можно писать return посередине функции генерации.


Использование теперь выглядит так:

struct node
{
    node* left_;
    node* right_;
    value_t value_;
};

generator(iterate_tree_t, node*) 
{
    node* root;
    node* curr;
    std::vector<node*> node_queue;

public:
    iterate_tree_t(node* root)
        : root (root)
        , curr ()
    {}

    emit(node*)
        if (0 == root)
            return; // <--- можно писать просто return - значит конец последовательности

        curr = root;
        node_queue.clear();
        node_queue.reserve(128);
        
        for (;;)
        {
            yield(curr);

            if (curr->left_ && curr->right_)
            {
                node_queue.push_back(curr->right_);
                curr = curr->left_;
            }
            else if (curr->left_)
            {
                curr = curr->left_;
            }
            else if (curr->right_)
            {
                curr = curr->right_;
            }
            else
            {
                if (node_queue.empty())
                    break;
                curr = node_queue.back();
                node_queue.pop_back();
            }
        }
    stop_emit(); // <--- не надо передавать реальный элемент
};

int main()
{
    iterate_tree_t iter (0);
    // <--- проверка теперь вначале цикла - т.е. поддерживает пустые последовательности
    while (iter.next())
        // <--- в теле можно вызывать iter.value() несколько раз - перехода не след. элемент не будет
        std::cout << "node: " << iter.value()->value_ << " " << iter.value()->value_ << std::endl;
    std::cout << "[end]" << std::endl;
}




Реализация:
template<typename derived_t, typename elem_t>
class generator_base
{
protected:
    int generator_line;
    elem_t generator_value;

public:
    generator_base()
        : generator_line()
    {}

    bool next()
    {
        int generator_cur_line = generator_line;
        generator_line = 0;
        static_cast<derived_t*>(this)->generate_value(generator_cur_line);
        return 0 != generator_line;
    }

    elem_t const& value()
    {
        return generator_value;
    }
};

#define generator(NAME, T) class NAME : public generator_base<NAME, T>

#define emit(T)\
    void generate_value(int generator_cur_line)\
    { switch(generator_cur_line) { case 0:;\
/**/

#define stop_emit() }}

#define yield(V)\
        do {\
            generator_line=__LINE__;\
            generator_value = (V);\
            return;\
            case __LINE__:;\
        } while (0)\
/**/



Теперь стало использовать немного попроще. Вот, например, обход дерева в глубину.
Кстати, если нужны локальные переменные внутри функции генерации, то их можно брать в expression block {}.

generator(iterate_tree_rec_t, node*) 
{
    node* root;
    std::vector<iterate_tree_rec_t> iter_queue;

public:
    iterate_tree_rec_t(node* root)
        : root (root)
    {}

    emit(node*)
        if (0 == root)
            return;

        yield(root);

        {
            // <--- локальная переменная
            size_t i = 0;
            for (; i != root->children_.size(); ++i)
                iter_queue.push_back(root->children_[i]);
        }

        while (iter_queue.size())
        {
            while (iter_queue.back().next())
                yield(iter_queue.back().value());
            iter_queue.pop_back();
        }

    stop_emit();
};