Goodbye, Q_FOREACH A Porting Guide to C++11 Ranged For-Loops
Q_FOREACH (or the alternative form,
foreach) will be deprecated soon, probably in Qt 5.9. Starting with Qt 5.7, you can use the
QT_NO_FOREACH define to make sure that your code does not depend on
You may have wondered what all the fuss is about. Why is there a continuous stream of commits going to into Qt replacing
Q_FOREACH with C++11 ranged for-loops? And why does it take so many commits and several Qt versions to port away from
Q_FOREACH? Can’t we just globally search and replace
Q_FOREACH (a, b) with
for (a : b) and be done with it?
Read on for the answers.
What is Q_FOREACH?
Q_FOREACH is a macro, added for Qt 4, that allows to conveniently iterate over a Qt container:
Q_FOREACH(int i, container) doSomethingWith(i); Q_FOREACH(const QString &s : functionReturningQStringList()) doSomethingWith(s);
It basically works by copying the second argument into a variable called
QForeachContainer, and then iterating over it. I’m only mentioning this for two reasons: First, you will start seeing that internal
QForeachContainer at some point in deprecation warnings (probably starting with Qt 5.9), and, second, yes, you heard correctly, it copies the container.
This copying has two effects: First, since the copy taken is essentially const, no detaching happens when iterating, unlike if you use the C++98 or C++11 alternatives:
for (QStringList::const_iterator it = container.begin(), end = container.end(); it != end; ++it) doSomethingWith(*it); for (const auto &s : container) doSomethingWith((*it);
In both cases the (explicit or implicit) calls to
end() cause a non-const
container to detach from shared data, ie. to perform a deep-copy to gain a unique copy of the data.
This problem is well-known and there are tools to detect this situation (e.g. Clazy), so I won’t spend more time discussing it. Suffice to say that
Q_FOREACH never causes detaches.
Except when it does.
Q_FOREACH is Convenient^WEvil
The second effect of
Q_FOREACH taking a copy of the container is that the loop body can freely modify the original container. Here’s a very, very poor implementation that takes advantage of this:
Q_FOREACH(const QString &lang, languages) languages += getSynonymsFor(lang);
Of course, since
Q_FOREACH took a copy, once you perform the first loop iteration,
languages will detach from that copy in
Q_FOREACH, but this kind of code is safe when using
Q_FOREACH, unlike when you use C++11 ranged for-loops:
for (const auto &lang : languages) languages += getSynonymsFor(lang); // undefined behaviour if // languages.size() + getSynonymsFor(lang).size() > languages.capacity()
So, as we saw,
Q_FOREACH is convenient—if you write code.
Things look a bit different if you try to understand code that uses
Q_FOREACH, because you often can’t tell whether the copy that
Q_FOREACH unconditionally takes is actually needed in any particular case, or not. A loop that plain falls apart if the container is modified while iterating is much easier to reason about than a
And this brings us to porting away from
Towards a Q_FOREACH-Free World
Things would be pretty simple if you could just globally search and replace
Q_FOREACH (a, b) with
for (a : b) and be done with it. But alas, it ain’t so easy…
We now know that the body of a
Q_FOREACH loop is free to modify the container it’s iterating over, and don’t even for a minute think that all cases are so easy to recognize as the example with the languages above. The modification of the container may be several functions deep in the call stack originating from the loop body.
So, the first question you need to ask yourself when porting a
Q_FOREACH loop is:
Does the loop body (directly or indirectly) modify the container iterated over?
If the answer is yes, you also need to take a copy and iterate over the copy, but as the nice guy that you are, you will leave a comment telling the future You why that copy is necessary:
const auto containerCopy = container; // doSomethingWith() may modify 'container' if .... for (const auto &e : containerCopy) doSomethingWith(e);
I should note that in cases where the container modification is restricted to appends, you can avoid the copy (and the detach caused by it) by using an indexed loop:
for (auto end = languages.size(), i = 0; i != end; ++i) // important: cache 'languages.size()' languages += getSynonymsFor(languages[i]);
With that question answered, the next one is:
What are you actually iterating over?
If your container is a
std:: container or
QVarLengthArray, you replace the
Q_FOREACH(a, b) with
for (a : b) and you are done. Arguably,
Q_FOREACH should never, ever have been used on such a container, since copying those always copies all elements (deep copy).
If your container is a const lvalue or a const rvalue, you can do the same. Const objects don’t detach, not even the Qt containers.
If your container is a non-const rvalue, simply store it in an automatic const variable, and iterate over that:
const auto strings = functionReturningQStringList(); for (const QString &s : strings) doSomethingWith(s);
Semantically, this is exactly what Q_FOREACH did before, so this case is easiest to verify (the body could not possibly have modified the container, as it didn’t have access to it).
Last, not least, if your container is a non-const lvalue, you have two choices: Make the container
const, or, if that doesn’t work, use
std::as_const() (new in C++17, but easily implemented yourself, if required) or
qAsConst() (new in Qt 5.7) to cast to const:
for (const QString &s : qAsConst(container)) doSomethingWith(s);
There, no detaches, no unnecessary copies. Maximum efficiency and maximum readability.
Here’s why you’ll want to port away from
Q_FOREACH, ideally to C++11 ranged for-loops:
Q_FOREACHis going to be deprecated soon.
- It only works efficiently on (some) Qt containers; it performs prohibitively expensive on all
QVarLengthArray, and doesn’t work at all for C arrays.
- Even where it works as advertised, it typically costs ~100 bytes of text size more per loop than the C++11 ranged for-loop.
- Its unconditionally taking a copy of the container makes it hard to reason about the loop.