Реализация enumerate_foreach на основе Boost foreach

Предисловие к этому вопросу: я реализовывал различные служебные функции C++ и (когда мне нужно) макросы в большем наборе инструментов для собственного использования. Недавно я делал множество макросов цикла на основе BOOST_FOREACH, а также итеративных сознательных функций.

Короче говоря, я столкнулся с трудностями при создании цикла перечисления, который использует BOOST_FOREACH, но с переданным дополнительным параметром, который увеличивается на каждую итерацию цикла. Это будет работать так же, как команда перечисления в Python, которую я нахожу полезной при циклическом переборе произвольных итераторов без добавления большого количества кода. Макрос принимает вид, подобный BOOST_FOREACH:

ENUMERATE_FOREACH(COUNT, VAR, COL)

и будет эффективно делать это, но не полагаясь на размещение неуравновешенного "}" для закрытия (или другого макроса окончания цикла):

COUNT = 0; BOOST_FOREACH(VAR, COL) { COUNT++; ...Rest of loop...

Теперь, прежде чем я получу обвинения в попытке сделать не-C++ оператор в C++, я полностью осознаю, что это встраивает чужую концепцию в C++, и я не пробую Pythonize C++ код. Мне просто любопытно, можно ли просто реализовать такой цикл с известными наборами инструментов / boost без экстремальных зависимостей. Наличие такого макроопределения исключило бы возможный источник ошибок для определенных стилей зацикливания, когда мне нужно считать счетчик при итерации, и это устраняет неоднозначность назначения переменной COUNT.

Я думал о создании обертки шаблона для переменной COL, прежде чем она перейдет в BOOST_FOREACH, но количество возможностей для COL делает это трудным / невозможным с некоторыми комбинациями итерируемых переменных без создания другой версии ENUMERATE_FOREACH и повторной реализации большого количества BOOST_FOREACH -- сложная и неумелая задача без огромных испытаний и времени.

Выполнение отдельной встроенной функции может сработать, но тогда синтаксис зацикливания нарушится, и я перехожу к большей части ситуации прохождения операторных функций стиля (что я уже реализовал).

Это оставило мне возможность взять последние строки foreach.hpp из библиотек boost и вставить свой собственный оператор приращения в дополнительный аргумент. Затем я становлюсь зависимым от улучшенной версии и беспокоюсь о новых обновлениях (любые изменения синтаксиса), чтобы ускорить нарушение моего хакерского пользовательского макроса.

Последний вариант, о котором я подумал, это сделать ENUMERATE_BEGIN и ENUMERATE_END, чтобы скрыть мою операцию приращения итератора. Этот подход более подвержен ошибкам, чем один макрос, поскольку пользователь должен поместить два макроса вместо одного, хотя это может быть единственным простым решением.

Я попытался осмотреть SO и другие источники, чтобы увидеть, пытался ли кто-то это делать раньше без особой удачи. Надеюсь, кто-то успешно поработал с такой концепцией реализации или имеет представление об изменении моего подхода. Если нет чистого способа сделать это, я могу просто продолжать свои циклы с count++, когда я хочу считать. Опять же, это скорее любопытство, и кто-то может предложить, чтобы одна из идей, о которых я говорил, была совершенно разумным подходом или настолько хороша, насколько это возможно.

2 ответа

Решение

Прочитав ответ Иоахима, я был в основном удовлетворен, но я пытался манипулировать BOOST_FOREACH до тех пор, пока я, по крайней мере, не смог бы сделать это очень хакерским способом, а потом я обнаружил, что на самом деле могу реализовать счетчик в мягкой манере, не переписывая весь макрос или не делая некоторые undeF#define заявления на BOOST_FOREACH_NEXT.

Я злоупотребляю тем фактом, что BOOST_FOREACH имеет оператор (VAR = derefence(...); ...) и помещаю структуру между VAR и = так, чтобы я получал (VAR = IncrementCountAndPassRHS = derefence(...); ...).

Я не тестировал много (пока) проблем макропроцессора, но думаю, что это безопасно в forloop.

РЕДАКТИРОВАТЬ Добавлены обновления, чтобы исправить проблемы перекрытия имен переменных области с несколькими циклами в одной строке.

namespace loopnamespace {
template<typename T>
void incrementT(T *t) {
    (*t)++;
}

struct IncrementCounterPassthrough {
    bool checker;
    boost::function<void(void)> incrementer;

    template<typename Count>
    IncrementCounterPassthrough(Count& t) {
        t = -1;
        checker = true;
        incrementer = boost::bind(&incrementT<Count>, &t);
    }

    template<typename T>
    T& operator=(T& rhs) {
        incrementer();
        return rhs;
    }
};
}

#define ENUMERATE_FOREACH(COUNT, VAR, COL)                                                                   \
    for(::loopnamespace::IncrementCounterPassthrough BOOST_FOREACH_ID(_foreach_count_pass)(COUNT);           \
        BOOST_FOREACH_ID(_foreach_count_pass).checker; BOOST_FOREACH_ID(_foreach_count_pass).checker = false)\
    BOOST_FOREACH(VAR = BOOST_FOREACH_ID(_foreach_count_pass), COL)

Позволяет мне сделать:

std::string hello( "Hello, boost world!" );
unsigned int value;
ENUMERATE_FOREACH( value, char ch, hello ) {
   std::cout << ch << " => " << value << "\n";
}

вывести:

H => 0
e => 1
l => 2
l => 3
o => 4
, => 5
  => 6
b => 7
o => 8
o => 9
s => 10
t => 11
  => 12
w => 13
o => 14
r => 15
l => 16
d => 17
! => 18

Вы могли бы решить это, имея версию с четырьмя аргументами ENUMERATE_FOREACHгде последний аргумент является предикатом для вызова.

Что-то вроде:

ENUMERATE_FOREACH(COUNT, VAR, COL, PRED)

и это расширилось бы до чего-то вроде

{
    COUNT = 0;
    BOOST_FOREACH(VAR, COL)
    {
        PRED(COUNT);
        COUNT++;
    }
}

Хорошая вещь в вышеупомянутом состоит в том, что предикат может быть лямбда-функцией C++11:

ENUMERATE_FOREACH(COUNT, VAR, COL,
                  [](int count){ cout << "count = " << count << '\n'; });

Изменить: Другой способ, это все еще использовать четвертый параметр, но пусть этот параметр будет действительным кодом. Например:

ENUMERATE_FOREACH(COUNT, VAR, COL, do { std::cout << COUNT << '\n'; } while (0))

будет расширяться до

{
    COUNT = 0;
    BOOST_FOREACH(VAR, COL)
    {
        do { std::cout << COUNT << '\n'; } while (0);
        COUNT++;
    }
}

Это может быть немного более грязно, чем, например, использование C++11 лямбд.

Редактировать 2: Если у вас есть C++11 и вы можете использовать лямбда-выражения, то, вероятно, у вас также есть новый цикл for, основанный на диапазоне, что означает, что вы можете вместо этого создать правильную функцию, что-то вроде:

template<typename SeqT, typename PredT>
void for_each_count(SeqT seq, PredT pred)
{
    int count = 0;
    for (auto val : seq)
    {
        pred(val, count);
        count++;
    }
}

Использование:

std::vector<int> v = {1, 2, 3, 4};
for_each_count(v,
    [](int v, int c){
        std::cout << "value = " << v << ", count = " << c << '\n';
    });

Выше будет печатать

значение = 1, количество = 0
значение = 2, количество = 1
значение = 3, количество = 2
значение = 4, количество = 3
Другие вопросы по тегам