Printing Custom Qt Types with qDebug()

When you define your own C++ types for a Qt application, you want to print their values with qDebug(), qWarning() or qCritical() eventually.

auto frame1 = QCanBusFrame{0x18ef0201U, QByteArray::fromHex("018A010000000000")};
qDebug() << "frame1 =" << frame1;

The C++ compiler will complain with this error message:

error: no match for ‘operator<<’ (operand types are ‘QDebug’ and ‘QCanBusFrame’)

The error message tells you to write your own output operator for the QDebug stream and for QCanBusFrame.

A custom C++ type that wants to play nicely with Qt should always define a QDebug output operator. Even the developers of the Qt library seem to forget it sometimes, as the example of QCanBusFrame shows.

The documentation of QDebug Class gives some hints about writing custom types to QDebug streams and links to the sections Providing Support for the qDebug() Stream Operator and Making the Type Printable. The first implementation of the stream operator for QCanBusFrame is a simple adaptation of the example given in the documentation of QDebug Class.

QDebug operator<<(QDebug debug, const QCanBusFrame &frame)
{
    QDebugStateSaver saver(debug);
    debug.nospace() << frame.toString();
    return debug;
}

The line

qDebug() << "frame1 =" << frame1;

prints

frame1 = "18EF0201   [8]  01 8A 01 00 00 00 00 00"

The line QDebugStateSaver saver(debug); saves the current state of the stream and restores this state when exiting the operator function. Hence, formatting changes by the custom operator don’t affect any other calls of operator<<. They stay local to the custom operator.

The QDebug stream has a special thing with spaces and with quotes. It inserts a space between each argument and encloses QString, QByteArray and QChar arguments with double quotes. With the inserted space replaced by “@”, the last output looks as follows:

frame1 =@"18EF0201   [8]  01 8A 01 00 00 00 00 00"

As "frame1 =" has the type const char *, it is printed without quotes. The CAN frame, however, is enclosed with quotes, because frame.toString() has the type QString.

The formatting option debug.nospace() ensures that the stream operator for QCanBusFrames does not insert its own space between the equality sign and the opening quote. If you wrote

    debug.space() << frame.toString();

in the custom operator, the output (again with “@” instead of space) would be

frame1 =@@"18EF0201   [8]  01 8A 01 00 00 00 00 00"

If you wrote

debug.maybeSpace() << frame.toString();

the custom operator would insert a space or not depending on the preceding formatting options in the QDebug stream.

qDebug() << "frame1 =" << frame1;
# frame1 =@@"18EF0201   [8]  01 8A 01 00 00 00 00 00"

qDebug().nospace() << "frame1 =" << frame1;
# frame1 ="18EF0201   [8]  01 8A 01 00 00 00 00 00"

qDebug().nospace() disables the automatic insertion of spaces and prevents debug.maybeSpace() from inserting a space. By default, the insertion of spaces is enabled and debug.maybeSpace() inserts a space.

The output format of QCanBusFrame::toString() isn’t ideal, because you cannot pass it to Linux CAN utilities like cansend and cangen. For example,

$ cansend can0 18EF0201#018A010000000000

writes frame1 to the CAN interface can0 from the Linux command line. The following implementation of operator<< produces the desired output format.

QDebug operator<<(QDebug debug, const QCanBusFrame &frame)
{
    QDebugStateSaver saver(debug);
    debug.nospace().noquote() 
        << QByteArray::number(frame.frameId(), 16) << "#"
        << frame.payload().toHex();
    return debug;
}

The option noquote() is required to suppress the quotes around the first and third argument, which are both QByteArrays.

The other message loggers qInfo(), qWarning(), qCritical() and qFatal() work with the custom type QCanBusFrame as well, because they all write to the QDebug stream.

Scroll to top