[Development] On removing functions from the API, but not the ABI
Marc Mutz
marc.mutz at qt.io
Wed Jul 13 17:04:33 CEST 2022
Hi,
On the occasion of it's first birthday[1], let me introduce you to
QT_REMOVED_SINCE, the "new" standard mechanism for non-deprecation API
evolution.
== The problem
As you might have seen in Qt headers, we often amend function overload
sets, e.g. adding defaulted arguments at the end, porting from int to
qsizetype, or from a string-ish overload set to QAnyStringView. Due to
binary compatibility, we cannot actually change the old functions[2], we
need to keep the old function in and add overloads that hopefully won't
cause ambiguities in existing calls. This is why QStringView was
engineed to not cause ambiguities with QString overloads in the same
set[3]: it allowed us to add QStringView overloads to existing QString
functions without having to worry about breaking user code.
But QByteArrayView/QByteArray and QString/QAnyStringView don't overload
properly (cause ambiguities), so just adding overloads and comments a la
// ### Qt 7: remove
// ### Qt 7: merge the above overloads
or even pre-programming for Qt 7 with
#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
Qt 6 code
#else
Qt 7 code
#endif
don't work anymore. We actually need to remove the QString/QByteArray
overload (or at least make it Q_WEAK_OVERLOAD) for the
QAnyStringView/QByteArrayView overload to not cause ambiguities.
So, if on hand, we can't remove existing function (overloads) because of
binary compatibility, yet need to remove them to avoid ambiguity errors
in existing users, what does that leave us with?
== The solution
The solution is to mentally separate ABI and API. For BC clients, all
that matters is the ABI, while for SC clients, all that matters is the
API. So, let's separate the two, and maintain them independently.
Enter QT_REMOVED_SINCE.
Suppose, you'd like to replace a function taking a QByteArray with one
that takes a QByteArrayView. This would be SC, but not BC (if the
function is exported). To dissociate API and ABI, we QT_REMOVE_SINCE the
old version and just add the new:
+ #if QT_MEEPLIB_REMOVED_SINCE(6, 4)
void setFoo(const QByteArray &foo);
+ #endif
+ void setFoo(QByteArrayView foo);
we can even add back the BA overload as a weak one:
+ Q_WEAK_OVERLOAD
+ void setFoo(const QByteArray &foo);
In the .cpp file, we just change the function:
± docs
- void Meep::setFoo(const QByteArray &foo)
+ void Meep::setFoo(QByteArrayView foo);
± impl
maybe add the weak overload, too:
+ docs
+ Q_WEAK_OVERLOAD_IMPL
+ void Meep::setFoo(const QByteArray &foo)
+ impl
So now we've done everything to make _new_ users pick up the new
function. The QT_CORE_REMOVED_SINCE removes the old function from the
API (it's not visible to the compiler anymore), but still we have a
loose end to tie up: the function is gone from the ABI, too. In order to
get it back, we need to add an implementation to a special file,
removed_api.cpp. If this file doesn't, yet, exists for the library you
work on, add it by mimicking [4]. Once it exists, find the
QT_<module>_REMOVED_SINCE(x, y) section in it that matches the version
of your QT_<module>_REMOVED_SINCE(x, y) in the header, and add the
following:
#if QT_MEEPLIB_REMOVED_SINCE(6, 4)
+#include "meep.h"
+
+ void Meep::setFoo(const QByteArray &foo)
+{
+ setFoo<>(foo); // calls the Q_WEAK_OVERLOAD
+}
+
// #include "qotherheader.h"
// // implement removed functions from qotherheader.h
// order sections alphabetically
#endif // QT_MEEPLIB_REMOVED_SINCE(6, 4)
et voila: the function is back in the ABI, but not in the API, just what
the doctor prescribed.
== PS: On qsizetype
If you look at existing uses, you sometimes will see an additional guard
QT_POINTER_SIZE != 4. This is because on 32-bit platforms,
std::is_same_v<qsizetype,int>, so we can't have
setMeepCount(int); // QT_REMOVED_SINCE
setMeepCount(qsizetype); // new
because they're literally the same function. So we remove the int one
only when it'd be different from the qsizetype one:
#if QT_MEEPLIB_REMOVED_SINCE(6, 4) && QT_POINTER_SIZE != 4
void setMeepCount(int count);
#endif
void setMeepCount(qsizetype count);
What needs to go into removed_api.cpp is left as an exercise to the reader.
Thanks,
Marc
[1] AuthorDate of https://codereview.qt-project.org/c/qt/qtbase/+/359671
and https://codereview.qt-project.org/c/qt/qtbase/+/362209
[2] unless they're inline and not inside an exported class (which is why
we shouldn't export non-polymorphic classes wholesale: it restricts what
we can do to inline functions)
[3] https://www.kdab.com/qstringview-diaries-masters-overloads/
[4] https://codereview.qt-project.org/c/qt/qtbase/+/421325
More information about the Development
mailing list