c++qtbackport

How to backport functionality in Qt?


Suppose that there is a project that uses Qt, and depends on the features (e.g. added class members) present in a new version of Qt. The project is meant to be built with a "system"/distribution version of Qt that may be older than the version the project depends on.

The naive approach results in the preprocessor hell:

void Class::foo() {
#if QT_VERSION >= QT_VERSION_CHECK(...)
  QClass::newFangled();
#else
  QClass::oldFangled1();
  blurble();
#endif
}

Is there a cleaner approach that won't depend on preprocessor macros at the point of use of the feature? I.e. we do not want:

// THIS IS BLAH
#if QT_VERSION >= QT_VERSION_CHECK(...)
#define QClass_newFangled QClass::newFangled
#else
#define QClass_newFangled [this]{ \
   QClass::oldFangled1(); \
   blurble(); \
}
#endif
void Class::foo() {
  QClass_newFangled();
}

Solution

  • A workable approach is to use a given class or global function in a custom namespace. The class/function in that namespace can be imported from Qt without changes, or be the custom one that backports the needed features.

    The code below is Qt-namespace-aware: it will work with a namespaced Qt build.

    At the point of use, the backported functionality (global functions and classes) are accessed in the compat namespace, i.e.

    void test() {
      ...
      qDebug() << compat::qEnvironmentVariableIntValue("RUNMODE");
      compat::QTimer::singleShot(1000, object, []{ ... });
    }
    

    Note that when adding functions/methods with default arguments to the compat namespace, the default arguments are propagated only in C++17 and later. Otherwise, we have to handle them ourselves by defining forwarding functions/methods.

    Backporting a global function

    Suppose that we want to backport qEnvironmentVariableIntValue from Qt 5.5:

    // INTERFACE
    
    namespace compat {
    #if QT_VERSION >= QT_VERSION_CHECK(5,5,0)
    #if __cplusplus >= 201703L
    using QT_PREPEND_NAMESPACE(qEnvironmentVariableIntValue); // C++17
    #else
    int inline qEnvironmentVariableIntValue(const char *varName, bool *ok = {}) {
      return QT_PREPEND_NAMESPACE(qEnvironmentVariableIntValue)(varName, ok);
    }
    #endif
    #else
    int qEnvironmentVariableIntValue(const char *varName, bool *ok = {});
    #endif
    } // compat
    
    // IMPLEMENTATION
    
    namespace compat {
    #if QT_VERSION < QT_VERSION_CHECK(5,5,0)
    using QT_PREPEND_NAMESPACE(qgetenv); // C++17
    int qEnvironmentVariableIntValue(const char *varName, bool *ok) {
      return qgetenv(varName).toInt(ok, 0);
    }
    #endif // Qt<5.5
    } // compat
    

    Backporting into a class

    Suppose that we want to backport Qt 5.4's QTimer::singleShot(int, QObject*, Functor), as motivated by this answer:

    // INTERFACE
    
    namespace compat {
    #if QT_VERSION >= QT_VERSION_CHECK(5,4,0)
    
    using QT_PREPEND_NAMESPACE(QTimer);
    
    #else
    
    using Q_QTimer = QT_PREPEND_NAMESPACE(QTimer);
    class QTimer : public Q_QTimer {
      Q_OBJECT
    public:
      #if __cplusplus >= 201703L
      using Q_QTimer::Q_QTimer; // C++17
      #else
      QTimer(QObject *parent = {}) : Q_QTimer(parent) {}
      #endif
      template <class Functor> static void singleShot(int, QObject *, Functor &&);
    };
    
    template <class Functor>
    void QTimer::singleShot(int msec, QObject *context, Functor &&fun) {
      ...
    }
    
    #endif
    } // compat