Sign up for the KDAB Newsletter
Stay on top of the latest news, publications, events and more.
Go to Sign-up
A small new feature that I have added to Qt 5.8 is the possibility of disabling narrowing conversions in the new-style QObject::connect
statement. In this short blog post I would like to share with you why I thought this was useful and therefore implemented it.
Since Qt 5.0, the new-style, PMF-based (pointer to member function-based) QObject::connect
will check at compile time if the signal's signature is compatible with the slot's one.
For instance, let's consider these two QObject
subclasses:
class Sender : public QObject
{
Q_OBJECT
signals:
void signalWithInt(int i);
void signalWithDouble(double d);
void signalWithQString(QString s);
};
class Receiver : public QObject
{
Q_OBJECT
public slots:
void slotWithInt(int i);
void slotWithDouble(double d);
void slotWithQString(QString s);
void slotWithQVariant(QVariant v);
};
Sender *s = new Sender;
Receiver *r = new Receiver;
This is what happens with various connect statements between them:
connect(s, &Sender::signalWithInt,
r, &Receiver::slotWithInt); // works
connect(s, &Sender::signalWithDouble,
r, &Receiver::slotWithDouble); // works
connect(s, &Sender::signalWithQString,
r, &Receiver::slotWithQString); // works
So far, nothing surprising -- a perfect match between the arguments will make the QObject::connect
statement happy.
Let's try some variations:
connect(s, &Sender::signalWithInt,
r, &Receiver::slotWithQString); // does not compile
connect(s, &Sender::signalWithQString,
r, &Receiver::slotWithInt); // does not compile
connect(s, &Sender::signalWithDouble,
r, &Receiver::slotWithQString); // does not compile
connect(s, &Sender::signalWithQString,
r, &Receiver::slotWithDouble); // does not compile
Here things become more interesting: since there is no conversion between an int
and a QString
(and vice versa), this code rightfully fails to compile; the same happens with double
.
In this case, the improvement that we get over the "old"-style QObject::connect
is that this error is at compile-time instead of runtime. Indeed, the same connect statements rewritten using the macro-based QObject::connect
will not work and generate warnings on the console. For instance:
connect(s, SIGNAL(signalWithInt(int)),
r, SLOT(slotWithQString(QString))); // compiles, fails at runtime
The new QObject::connect
has another little-known interesting feature: it allows the compiler to perform implicit conversions between the arguments of the signal and the slot.
For instance:
// using int -> double
connect(s, &Sender::signalWithInt,
r, &Receiver::slotWithDouble); // compiles and works as expected
// using the implicit QVariant(QString) ctor
connect(s, &Sender::signalWithQString,
r, &Receiver::slotWithQVariant); // compiles and works as expected
This is just not possible at all using the old-style syntax; one would need workarounds such as a "trampoline slot" that does the conversion and emits another signal with the converted argument.
However, having implicit conversions also means that this statement succeeds:
// double -> int conversion
connect(s, &Sender::signalWithDouble,
r, &Receiver::slotWithInt); // compiles and "works"
This may be unexpected, as people usually assume this conversion will not work. After all, while converting an int
to a double
is always possible without loss of precision, converting a double
to an int
may lose precision or just not be possible (if the double
is out of range).
Unfortunately, this conversion is fully allowed by C++. Consider it, if you prefer, one of the C remnants in the C++ language. That is, this code:
double d = 3.14;
int i = d;
This is 100% legal C++ that compiles and works as expected (d
is truncated; if it can't fit into an int
, the program has undefined behavior).
Only with C++11 one can forcibly disable narrowing conversions, by using the new uniform initialization syntax (so it's effectively an opt-in feature). For instance:
double d = 3.14;
int i{d};
Although many compilers just warn about this, this C++ code is ill-formed, because of the narrowing conversion from double to int.
Since we don't like dealing with undefined behavior, I wondered if it could be possible to achieve the same for Qt. That is, could we disable implicit narrowing conversions in QObject::connect
?
I went ahead and implemented the necessary modifications. Starting with Qt 5.8 one can define the QT_NO_NARROWING_CONVERSIONS_IN_CONNECT
macro to disable the implicit conversions that narrow (if you are curious, I implemented it in this patch).
If you are using qmake, add this to your .pro
file:
DEFINES += QT_NO_NARROWING_CONVERSIONS_IN_CONNECT
# ... other DEFINES for your project ...
With this macro defined, QObject::connect
statements that would narrow the arguments do not compile any longer:
// under QT_NO_NARROWING_CONVERSIONS_IN_CONNECT defined
connect(s, &Sender::signalWithDouble,
r, &Receiver::slotWithInt); // does not compile!
This functionality is opt-in, as making it the default (and opt-out) may break valid source code. In the Qt Project we try to never introduce gratuitous source-incompatible changes.
My personal recommendation would be to always define QT_NO_NARROWING_CONVERSIONS_IN_CONNECT in your projects. It will contribute to your projects' "hygiene factor" by removing the possibilities of dangerous connect statements. For this reason, Qt itself uses it on the entirety of its build (see here).
Also, don't forget that the Qt Project is open to contributions. Every time you think "it would be great if Qt could do this little thing for me...", don't be afraid, and contribute to Qt yourself!
About KDAB
The KDAB Group is a globally recognized provider for software consulting, development and training, specializing in embedded devices and complex cross-platform desktop applications. In addition to being leading experts in Qt, C++ and 3D technologies for over two decades, KDAB provides deep expertise across the stack, including Linux, Rust and modern UI frameworks. With 100+ employees from 20 countries and offices in Sweden, Germany, USA, France and UK, we serve clients around the world.
Stay on top of the latest news, publications, events and more.
Go to Sign-up
Upgrade your applications from Qt 5 to Qt 6 with KDAB’s migration services. Get a free migration assessment and join a hands-on workshop to prepare your team for a successful transition!
Learn more
Learn Modern C++
Our hands-on Modern C++ training courses are designed to quickly familiarize newcomers with the language. They also update professional C++ developers on the latest changes in the language and standard library introduced in recent C++ editions.
Learn more
2 Comments
12 - Apr - 2017
Tomaz Canabrava
awesome, and I also think that this should be enabled by default.
12 - Apr - 2017
Giuseppe D'Angelo
Hi, I agree, but making it opt-out instead of opt-in is a source incompatible change, and those are frowned upon in Qt; maybe we'll make it opt-out in Qt 6. A discussion of which kind of source incompatible changes are acceptable within the same major version of Qt is going on here: https://codereview.qt-project.org/#/c/182311/ .
Cheers,