Реализация 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