Ответ 1
Версия GCC
GCC один очень прост. Прежде всего, он используется следующим образом:
Q_FOREACH(x, cont)
{
// do stuff
}
И это будет расширен до
for (QForeachContainer<__typeof__(cont)> _container_(cont); !_container_.brk && _container_.i != _container_.e; __extension__ ({ ++_container_.brk; ++_container_.i; }))
for (x = *_container_.i;; __extension__ ({--_container_.brk; break;}))
{
// do stuff
}
Итак, прежде всего:
for (QForeachContainer<__typeof__(cont)> _container_(cont); !_container_.brk && _container_.i != _container_.e; __extension__ ({ ++_container_.brk; ++_container_.i; }))
Это фактический цикл for
. Он устанавливает QForeachContainer
, чтобы помочь с итерацией. Переменная brk
инициализируется на 0. Затем выполняется условие:
!_container_.brk && _container_.i != _container_.e
brk
равно нулю, поэтому !brk
истинно, и предположительно, если в контейнере есть какие-либо элементы i
(текущий элемент) еще не равен e
(последний элемент).
Затем вводится тело этого внешнего for
, которое:
for (variable = *_container_.i;; __extension__ ({--_container_.brk; break;}))
{
// do stuff
}
Итак, x
установлен в *_container_.i
, который является текущим элементом, в котором итерация включена, и нет никакого условия, поэтому предположительно этот цикл будет продолжаться вечно. Затем вводится тело цикла, которое является нашим кодом, и это просто комментарий, поэтому он ничего не делает.
Затем вводится инкрементная часть внутреннего цикла, что интересно:
__extension__ ({--_container_.brk; break;})
Он уменьшает brk
так, что теперь -1 и выходит из цикла (с __extension__
, который делает GCC не выдавать предупреждений для использования расширений GCC, как вы знаете).
Затем вводится инкрементная часть внешнего цикла:
__extension__ ({ ++_container_.brk; ++_container_.i; })
который снова увеличивает brk
и снова делает его 0, а затем i
увеличивается, поэтому мы переходим к следующему элементу. Условие проверено, и поскольку brk
теперь 0, а i
предположительно еще не равно e
(если у нас больше элементов), процесс повторяется.
Почему мы уменьшили и затем увеличиваем brk
так? Причина в том, что часть приращения внутреннего цикла не будет выполнена, если мы использовали break
в теле нашего кода, например:
Q_FOREACH(x, cont)
{
break;
}
Тогда brk
по-прежнему будет 0, когда он выйдет из внутреннего цикла, а затем будет введена инкрементная часть внешнего цикла и увеличена до 1, тогда !brk
будет ложной, а условие внешнего цикла будет оцениваться как false, а foreach остановится.
Хитрость заключается в том, чтобы понять, что существует две петли for
; внешнее одно время жизни - это весь foreach, но внутренний - только для одного элемента. Это будет бесконечный цикл, так как он не имеет условия, но он либо break
ed из него увеличивает часть, либо break
в коде, который вы ему предоставляете. Вот почему x
выглядит так, что ему присваивается "только один раз", но на самом деле он назначается на каждой итерации внешнего цикла.
Версия VS
Версия VS немного сложнее, потому что она должна обойти отсутствие расширения GCC __typeof__
и блок-выражений, а версия VS, написанная для (6), не имела auto
или другие необычные возможности С++ 11.
Посмотрим на пример расширения для того, что мы использовали ранее:
if(0){}else
for (const QForeachContainerBase &_container_ = qForeachContainerNew(cont); qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->condition(); ++qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->i)
for (x = *qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->i; qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->brk; --qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->brk)
{
// stuff
}
if(0){}else
заключается в том, что VС++ 6 неверно оценивает переменные for
, а переменная, объявленная в части инициализации цикла for
, может использоваться вне цикла. Так что это обходной путь для ошибки VS. Причина, по которой они делали if(0){}else
вместо просто if(0){...}
, заключается в том, что после цикла нельзя добавить else
, например
Q_FOREACH(x, cont)
{
// do stuff
} else {
// This code is never called
}
Во-вторых, посмотрим на инициализацию внешнего for
:
const QForeachContainerBase &_container_ = qForeachContainerNew(cont)
Определение QForeachContainerBase
:
struct QForeachContainerBase {};
И определение qForeachContainerNew
есть
template <typename T>
inline QForeachContainer<T>
qForeachContainerNew(const T& t) {
return QForeachContainer<T>(t);
}
И определение QForeachContainer
есть
template <typename T>
class QForeachContainer : public QForeachContainerBase {
public:
inline QForeachContainer(const T& t): c(t), brk(0), i(c.begin()), e(c.end()){};
const T c;
mutable int brk;
mutable typename T::const_iterator i, e;
inline bool condition() const { return (!brk++ && i != e); }
};
Таким образом, чтобы восполнить недостаток __typeof__
(который аналогичен decltype
С++ 11), мы должны использовать полиморфизм. Функция qForeachContainerNew
возвращает значение QForeachContainer<T>
по значению, но из-за продления времени жизни во времени, если мы сохраним его в const QForeachContainer&
, мы можем продлить его срок службы до конец внешнего for
(фактически if
из-за ошибки VC6). Мы можем хранить QForeachContainer<T>
в QForeachContainerBase
, потому что первый является подклассом последнего, и мы должны сделать его ссылкой как QForeachContainerBase&
вместо значения, подобного QForeachContainerBase
, чтобы избежать нарезки.
Тогда для условия внешнего for
:
qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->condition();
Определение QForeachContainer
равно
inline const QForeachContainer<T> *qForeachContainer(const QForeachContainerBase *base, const T *) {
return static_cast<const QForeachContainer<T> *>(base);
}
И определение qForeachPointer
есть
template <typename T>
inline T *qForeachPointer(const T &) {
return 0;
}
Здесь вы можете не знать, что происходит, поскольку эти функции кажутся бессмысленными. Ну вот, как они работают и почему они вам нужны:
У нас есть QForeachContainer<T>
, хранящийся в ссылке на QForeachContainerBase
, без возможности вернуть его (что мы видим). Мы должны каким-то образом привести его к соответствующему типу и что там, где присутствуют две функции. Но как мы узнаем, к какому типу он должен был его сбрасывать?
Правило тройного оператора x ? y : z
заключается в том, что y
и z
должны быть одного типа. Нам нужно знать тип контейнера, поэтому мы используем функцию qForeachPointer
для этого:
qForeachPointer(cont)
Возвращаемый тип qForeachPointer
равен T*
, поэтому мы используем вывод типа шаблона для вывода типа контейнера.
true ? 0 : qForeachPointer(cont)
должен передать указатель NULL
правильного типа на QForeachContainer
, чтобы он знал, для какого типа следует использовать указатель, который мы ему передаем. Почему мы используем тернарный оператор для этого вместо того, чтобы просто делать qForeachContainer(&_container_, qForeachPointer(cont))
? Это позволит избежать оценки cont
много раз. Второй (фактически третий) операнд ?:
не оценивается, если условие false
, и поскольку это условие true
, мы можем получить правильный тип cont
, не оценивая его.
Итак, это решает это, и мы используем QForeachContainer
для перевода _container_
в нужный тип. Вызов:
qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))
И снова определение
inline const QForeachContainer<T> *qForeachContainer(const QForeachContainerBase *base, const T *) {
return static_cast<const QForeachContainer<T> *>(base);
}
Второй параметр всегда будет NULL
, потому что мы делаем true ? 0
, который всегда оценивается как 0
, и мы используем qForeachPointer для вывода типа T
и используем его, чтобы передать первый аргумент в QForeachContainer<T>*
, поэтому мы можем использовать его функции-члены/переменные с условием (все еще во внешнем for
):
qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->condition()
И condition
возвращает:
(!brk++ && i != e)
который совпадает с версией GCC выше, за исключением того, что после его оценки он увеличивает brk
. Итак, !brk++
оценивается до true
, а затем brk
увеличивается на 1.
Затем мы вводим внутренний for
и начинаем с инициализации:
x = *qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->i
Который просто устанавливает переменную в значение, на которое указывает итератор i
.
Тогда условие:
qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->brk
Так как brk
равно 1, вводится тело цикла, что является нашим комментарием:
// stuff
Затем вводится инкремент:
--qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->brk
Это уменьшает brk
до 0. Затем условие снова проверяется:
qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->brk
И brk
равно 0, которое равно false
, и цикл завершен. Мы приходим к инкрементной части внешнего for
:
++qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->i
И это увеличивает i
на следующий элемент. Тогда мы переходим к условию:
qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->condition()
Что проверяет, что brk
равно 0 (это оно) и увеличивает его до 1 снова, и процесс повторяется, если i != e
.
Это обрабатывает код break
только в том же порядке, что и версия GCC, так как brk
не будет уменьшаться, если мы используем break
в нашем коде, и все равно будет 1, а condition()
будет false для внешнего цикла, а внешний цикл будет break
.
И как сказано в комментариях GManNickG, этот макрос очень похож на Boost BOOST_FOREACH
, который вы можете прочитать здесь здесь. Итак, у вас есть это, надеюсь, что это поможет вам.