[Development] Converting types in Qt

Jędrzej Nowacki jedrzej.nowacki at digia.com
Tue Jul 15 08:55:29 CEST 2014


Hi,

    I would like to discuss type conversions in Qt. As you may know, Qt has 
the ability to convert a known type to another known type. This works for 
trivial cases like, for example, "int" to "long", but also for more complex 
ones like "QByteArray" to "QString" or "CustomType" to "OtherCustomType". Type 
conversion in Qt happens mostly without user interaction, for example in 
QObject introspection or in QML, but it also can be used through public API:
  - QVariant::convert -> converts wrapped value to target type
  - QVariant::canConvert -> fast check if it may be possible to convert 
wrapped type to a given target, which is, in my opinion, pretty useless, 
unless the real conversion is really heavy
  - QMetaType::registerConverter -> allows to register a custom converter 
function for a user defined type
  - QMetaType::convert -> perform conversion between two types

    I would like to agree on some rules, regarding conversions, as the current 
approach is chaotic:

  1. Are we allowed to add new conversions?

     The question is tricky because adding a new conversion is a behavior 
change, as this code:
     if (variant.canConvert(QMetaType::int)) ...
     may work differently. If we add custom types into the mix, everything is 
even more complex.

     1.1 Patch release or minor release only?

         I would say that new conversions may appear only in minor releases, 
obvious fixes to existing conversions may appear in patch releases, where by 
obvious I mean crash fixes and cases in which the returned value is definitely 
bogus.

     1.2 Should conversion be perfect or based on a best effort?

         Some of the conversion API returns a "bool" value which is a status 
of conversion. What should it return if a conversion is not perfect, for 
example "int(-1) -> uint" or "QVariantList{string, int, myobject} -> 
QStringList", should such a case be detected? How do we define the perfect 
conversion? Sometimes only ideal, data lossless, conversions should be 
allowed, for example QtTestLib is quite strict about it and it allows only 
safe conversions. So, for example, "int -> long" but not "uint -> int", but I 
guess for normal use cases such strictness is not necessary.
         I think we should base conversions on the best effort and set the 
status to false only if a conversion failed completely, that is mainly if a 
conversion is unknown or if underlying implementation detected a failure, like 
"QByteArray -> float" which uses QByteArray::toFloat(bool *ok)

     1.3 Should we try to support a user's type conversions out of the box?

         Currently a user needs to manually register a conversion function so 
Qt can know it and use it. For certain types we can do much better, because we 
can automatically convert some types. For example:
         QVector<char> -> QLinkedList<int>
         QList<Foo> -> QVector<Foo>
         QPointer<Foo> -> QObject*
         QPointer<Foo> -> void*
         QSharedDataPointer<Foo> -> bool
         MyQObject* -> QPointer<MyQObject>
         Currently we are not doing it for one reason which is behavior 
compatibility. What if a user already defined a conversion that we want to add? 
It could happen because the conversion was not available in a previous Qt 
version. The problem is that the new conversion function may behave in a 
different way, especially in edge cases and because of the lack of perfection 
mentioned in 1.2. We need to pick only one function. That could be the Qt 
version, but then we risk that an existing code will not work anymore. Are we 
willing to accept that?
         I believe that we should document the problem, and allow the 
conversions.

     1.4 Should a user be able to add Qt types conversion on his own?

         Some conversions are missing, some we consider as not safe. A user, 
assuming that he knows what he is doing, could register a conversion; for 
example, "QString -> QChar", how bad is it? Currently, such usage is blocked, 
because we are afraid that in the future we may add a conversion that 
overrides it.
         In my opinion it is not needed; it is a corner case, because we a) 
should have the conversion and then it will appear in a future version b) the 
conversion is invalid, and it is a sign of a user's broken code.


  2. Can we modify an existing conversion?

     All modification changes behavior. Of course we assume that changes are 
sensible, but still it may break existing code.

     2.1 Can we improve a conversion?

         For example, "QStringList -> QString" is very tempting, as it works 
only if the list is of size 1. The conversion could join all strings instead, 
it would be almost backward compatible, as we would alter only conversions 
that failed previously.
         I believe we should allow that; I can not wait for the bike-shedding 
about which character or string we should use to join them.

     2.2 How much we can break?

         Is missing data in the result of a conversion a reasonable cause to 
break behavior? For example "QVariant(QColor) -> QString" doesn't include the 
alpha channel (QTBUG-37851).
         As I said in 1.2, I think that we should take the approach of the 
best effort and nobody should depend on the exact result of a conversion. We 
should reserve our right to improve it, so the best effort is not a lie.

     2.3 How to decide if a conversion should be improved?

         A conversion may be arbitrary. For example, most of the conversions 
to QString. Should "bool(false) -> QString" return "False", "0", "false"? What 
about precision of, for example, "double -> QString" ?


  3. Can we remove existing conversions?

     I would say, no, but maybe in a major release.

Cheers,
  Jędrek



More information about the Development mailing list