I've recently found we can take advantage of some of Qt's newer features to achieve a basic level of reflection which I'll present. But what is reflection and what has been proposed for C++?
multi-platform applications. Non-GUI programs can be also developed, such as command-line tools and consoles for servers. Runs on all major desktop platforms and most mobile or embedded platforms. Supports various compilers, including the GCC C++ compiler and the Visual Studio suite and has extensive internationalization support. Qt also provides Qt Quick, that includes a declarative scripting language called QML that allows using JavaScript to provide the logic. Other features include SQL database access, XML parsing, JSON parsing, thread management and network support.
of all Qt objects. QObject is the heart of the Qt Object Model. The central feature in this model is a very powerful mechanism for seamless object communication called signals and slots . You can connect a signal to a slot with connect() and destroy the connection with disconnect() . QObject s organize themselves in object trees. When you create a QObject with another object as parent, the object will automatically add itself to the parent's children() list. The parent takes ownership of the object; i.e. it will automatically delete its children in its destructor. You can look for an object by name and optionally type using findChild() or findChildren() .
the program that handles Qt's C++ extensions. The moc tool reads a C++ header file. If it finds one or more class declarations that contain the Q_OBJECT macro, it produces a C++ source file containing the meta-object code for those classes. Among other things, meta-object code is required for the signals and slots mechanism, the run-time type information, and the dynamic property system. The C++ source file generated by moc must be compiled and linked with the implementation of the class. If you use qmake to create your makefiles, build rules will be included that call the moc when required, so you will not need to use the moc directly.
improves the type safety. If we turn on C4062 in Visual Studio (which is on by default in GCC) we get a warning if an enum class enumerator isn't handled in a switch statement. This is useful to spot where new enumerators need to be handled so we can change the code.
eColor { Red = 1, Blue = 2, Green = 3 }; tColoredObject() : m_color(tColoredObject::eColor::Red) {} private: eColor m_color; }; Q_DECLARE_METATYPE(tColoredObject::eColor); int main() { qRegisterMetaType(tColoredObject::eColor); ... } void HandleColor(tColoredObject::eColor color) { switch (color) { case tColoredObject::eColor::Red: qDebug("Red"); break; case tColoredObject::eColor::Blue: qDebug("Blue"); break; case tColoredObject::eColor::Green: qDebug("Green"); break; } } But this means the assert we get if the enum class is an undefined value has gone so it's not an exact replacement.
= 5; // error: cannot convert 'int' to 'tColoredObject::eColor' in initialization color = static_cast<tColoredObject::eColor>(5); // ok [expr.static.cast]/10: A value of integral or enumeration type can be explicitly converted to a complete enumeration type. The value is unchanged if the original value is within the range of the enumeration values (7.2). Otherwise, the behavior is undefined.
Minimum range of an enum of type T. //! It is expected that the enum shall provide a total template specialization //! for the enum of type T, so this function should not be called. //----------------------------------------------------------------------------- template <typename T> T EnumRangeMin() { BOOST_STATIC_ASSERT_MSG(sizeof(T) == 0, "EnumRangeMin - No template specialization for the EnumRangeMin function for the given enum"); // If you receive this compile time message it means you need to supply a template specialization // for the enum type for which you tried to call 'ConvertIntToEnumLinearRange' e.g.: // template<> eMyEnum EnumRangeMin<eMyEnum>() {return eMinValueForMyEnum;} return static_cast<T>(0); } //----------------------------------------------------------------------------- //! Base template function definition the Maximum range of the enum of type T. //! It is expected that the enum shall provide a total template specialization //! for the enum of type T, so this function should not be called. //----------------------------------------------------------------------------- template <typename T> T EnumRangeMax() { BOOST_STATIC_ASSERT_MSG(sizeof(T) == 0, "EnumRangeMax - No template specialization for the EnumRangeMax function for the given enum"); // If you receive this compile time message it means you need to supply a template specialization // for the enum type for which you tried to call 'ConvertIntToEnumLinearRange' e.g.: // template<> eMyEnum EnumRangeMax<eMyEnum>() {return eMaxValueForMyEnum;} return static_cast<T>(0); }
to an enum. The value to be converted //! must lie within an enum linear range where: //! EnumRangeMin<T>() <= \a val <= EnumRangeMax<T>() //! If the value that is supplied does not lie within the range the result is either //! the minimum (when too small) or maximum (when too large) range value of the enum. //----------------------------------------------------------------------------- template <typename T, typename V> T ConvertValueToEnumLinearRange(V val) { // If the value is outside the linear range then, we constrain the result to either min or max T minRange = EnumRangeMin<T>(); if (val < static_cast<V>(minRange)) return minRange; T maxRange = EnumRangeMax<T>(); if (val > static_cast<V>(maxRange)) return maxRange; // The value exists within the range, so provide the converted the value return static_cast<T>(val); }
enum value to a QString. The enum must be declared as Q_ENUM. //----------------------------------------------------------------------------- template <typename T, typename V> QString ConvertQEnumValueToString(V val) { const QMetaEnum metaEnum = QMetaEnum::fromType<T>(); const char* str = metaEnum.valueToKey(static_cast<int>(val)); return QString::fromUtf8(str); }
PM To: [email protected] Subject: Use QMetaEnum::keyCount() to initialise array Is it possible to use QMetaEnum::keyCount() to initialise an array? Something like: const QMetaEnum metaEnum = QMetaEnum::fromType<MyArray>(); int MyArray[metaEnum.keyCount()]; It seems like Q_ENUM declares functions with Q_DECL_CONSTEXPR in C++11 but I can't figure out how to get this to work.
PM Compiles for me (with g++ 8.2, -std=gnu++11 which is expanded from CONFIG += c++11). From: Nikos Chantziaras [email protected] Sent: Thursday, December 20, 2018 6:22 AM Unfortunately, that's a variable length array, which is a GNU extension.
AM Are you sure they're constexpr? From what I can see in Qt 5.12, keyCount() is not constexpr. It's just const. From: Tom Isaacson [email protected] Sent: Monday, December 31, 2018 8:29 AM You're right, keyCount() isn't. My confusion is that Q_ENUM declares its functions as constexpr: #define Q_ENUM(ENUM) \ friend Q_DECL_CONSTEXPR const QMetaObject *qt_getEnumMetaObject(ENUM) Q_DECL_NOEXCEPT { return &staticMetaObject; } \ friend Q_DECL_CONSTEXPR const char *qt_getEnumName(ENUM) Q_DECL_NOEXCEPT { return #ENUM; } But I don't understand why if the functions that then use them aren't.
AM To: [email protected] Subject: Use QMetaEnum::keyCount() to initialise array Is it possible to use QMetaEnum::keyCount() to initialise an array? Something like: const QMetaEnum metaEnum = QMetaEnum::fromType<MyArray>(); int MyArray[metaEnum.keyCount()]; After asking this on the Qt-Interest forum I spent a bit of time investigating. Q_ENUM declares functions with Q_DECL_CONSTEXPR: #define Q_ENUM(ENUM) \ friend Q_DECL_CONSTEXPR const QMetaObject *qt_getEnumMetaObject(ENUM) Q_DECL_NOEXCEPT { return &staticMetaObject; } \ friend Q_DECL_CONSTEXPR const char *qt_getEnumName(ENUM) Q_DECL_NOEXCEPT { return #ENUM; }
T> static QMetaEnum fromType() { Q_STATIC_ASSERT_X(QtPrivate::IsQEnumHelper<T>::Value, "QMetaEnum::fromType only works with enums declared as Q_ENUM or Q_FLAG"); const QMetaObject *metaObject = qt_getEnumMetaObject(T()); const char *name = qt_getEnumName(T()); return metaObject->enumerator(metaObject->indexOfEnumerator(name)); } Where it breaks is the last line, because QMetaObject::indexOfEnumerator() uses d.data: int QMetaObject::indexOfEnumerator(const char *name) const { const QMetaObject *m = this; while (m) { const QMetaObjectPrivate *d = priv(m->d.data);
data const QMetaObject *superdata; const QByteArrayData *stringdata; const uint *data; I don't know how the Meta-Object Compiler creates this but surely it's possible to change it to be constexpr?
AM Because the information is not known to the compiler at compile time. You need moc's parsing of the header in order for it to count how many enumerators are there in the enumeration. Like you said, if we had static reflections, we could do this entirely in the compiler -- we would do possibly ALL of moc as part of reflections. But until that happens, moc needs to run and will produce a .cpp with the count.
access the enumeration count in constexpr time. After all, QMetaEnum::keyCount is simply: const int offset = priv(mobj->d.data)->revision >= 8 ? 3 : 2; return mobj->d.data[handle + offset]; Note that moc produces those arrays as "static const", not "constexpr", so it may not be accessible today at constexpr evaluation time. Or it could. I don't remember if "static const" counts as constant expression... The problem is that you have to #include the moc's output in the TU that wants to create the OP's array. And it must be #include'd or built by the build system exactly once.
PM Not really, even if it can generate things in a header for templates, it still generates the actual data in a .cpp file. As it was said before, we can't really have the contents of the QMetaObject. Even verdigris does the same (putting the QMetaObject data in the .cpp) One would have to do more research to find out if it is possible. Maybe putting more data in the QMetaEnum directly. Not sure if this is possible in a binary compatible way. https://github.com/woboq/verdigris
meeting (Kona) Reflection TS v1 (David Sankel, Axel Naumann) completed. The Reflection TS international comments have now been processed and the TS is approved for publication. As I mentioned in other trip reports, note again that the TS’s current template metaprogramming-based syntax is just a placeholder; the feedback being requested is on the core “guts” of the design, and the committee already knows it intends to replace the surface syntax with a simpler programming model that uses ordinary compile-time code and not <>-style metaprogramming.
8: 09:00 - 10:30 What is the C++ Reflection TS and what will it do for me? The answer: a lot. This talk explains this exciting new language feature, demonstrates how it is used, and discusses the direction reflection is taking within the C++ standardization committee. David Sankel is a co-author of the Reflection TS and currently serves as its project editor.
<class T, std::size_t Size> constexpr typename array<T, Size>::reference array<T, Size>::at(std::size_t n) { if (n >= Size) throw std::out_of_range("array::at"); // compilation error here return elements_[n]; } So for now try-catch blocks are allowed but they're basically no-ops because throws aren't allowed. If you hit a throw at compile-time you just get a compiler error.
constexpr builtins, e.g. __builtin_memcpy . Raw memory allocation, e.g. malloc . Other annotations, e.g. Undefined Behaviour Sanitizer (UBSan) in libc++.
{1, 2, 3}; v.clear(); return 0; } // is_constant_evaluated() true, no annotations constexpr int X = f(); // is_constant_evaluated() false, normal runtime code int Y = f();
constexpr! (P1073: Immediate function) constexpr! int square(int x) { return x * x; } constexpr int x = square(3); // OK int y = 3; int z = square(y); // ERROR: square(y) is not a constant expression Functions don't exist at runtime.
cases. Allow persisting data structures to the data segment. Allow writing different code for constexpr and runtime when needed. Require compile-time evaluation with constexpr!
can already do some of it: type_traits, operators like sizeof struct Foo { int x; int y; }; constexpr std::size_t = sizeof(Foo); constexpr bool is_aggregate = std::is_aggregate_v(Foo);
} using MetaFoo = reflexpr(Foo); // Returns magic type using Members = std::reflect::get_data_members_t<MetaFoo>; // Another magic type using MetaX = std::reflect::get_element_t<0, Members>; // Not an int! constexpr bool is_public = std::reflect::is_public_v<MetaX>; using X = std::reflect::get_reflected_type_t<Metax>; // This is int!
the name of an entity as a string. Get member types/enums/etc of a type. Get base classes of a type. Get whether variable is static / constexpr . Get properties of base classes: virtual / public /etc More features planned in the future: Reflecting functions: P0670 Plans to reflect on arbitrary expressions too.
on top of constexpr notation. struct Foo { int x; long y; } constexpr std::reflect::Record meta_foo = reflexpr(Foo); constexpr std::vector members = meta_foo.get_data_members(); constexpr std::reflect::RecordMember meta_x = members[0]; constexpr bool is_public = meta_x.is_public(); constexpr std::reflect::Type x = meta_x.get_reflected_type(); using X = unreflexpr(x); // This is int! Need unreflexpr to translate from metadata back to type system.
to: std::ostream& operator<<(std::ostream& out, Color color) { constexpr std::vector enumerators = reflexpr(Color.get_enumerators(); { constexpr std::reflect::Enumerator enumerator = enumerators[0]; if (color == enumerator.get_constant()) { out << enumerator.get_name(); } } { constexpr std::reflect::Enumerator enumerator = enumerators[1]; if (color == enumerator.get_constant()) { out << enumerator.get_name(); } } // ... return out; } so you can have a different type at each step of the for... loop.
string injection.~~ 2. ~~Programmatic API.~~ 3. Token-sequence injection. Go to Herb's keynote: "Thoughts on a more powerful and simpler C++ (5 of N)" https://www.youtube.com/watch?v=80BZxujhY38 (First half is on CPPX, second on Metaclasses.)
knew which mutex covered which data, we could diagnose "oops, forgot to lock" and "oops, took the wrong lock". A manual discipline to group data with its mutex: struct MyData { vector<int>& v() { assert(m_.is_held() ); return v_; } Widget*& w() { assert(m_.is_held() ); return w_; } void lock() { m_.lock(); } bool try_lock() { return m_.try_lock(); } void unlock() { m_.unlock(); } private: vector<int> v_; Widget* w_; mutex_type m_; } Resonable migration from existing source (perhaps just add "()"). Repetitive: It would be nice to automate the boring parts...
) don't provide a way to ask "have I acquired this?". A simple wrapper to the rescue: template<typename Mutex> class TestableMutex { public: void lock() { m.lock(); id = this_thread::get_id(); } void unlock() { id = thread::id(); m.unlock(); } bool try_lock() { bool b = m.try_lock(); if (b) id = this_thread::get_id(); return b; } bool is_held() { return id == this_thread::get_id(); } private: Mutex m; atomic<thread::id> id; } // for recursive mutexes, can also add a count.
#define GUARDED_WITH(MutType) \ public: void lock() { mut_.lock(); } \ public: bool try_lock() { mut_.try_lock(); } \ public: void unlock() { mut_.unlock(); } \ private: TestableMutex<MutType> mut_; // Have to use token pasting to make a different member name for the private member than for the accessor. // Otherwise the user would have to specify two names - one for the the public name and the other for the private name. #define GUARDED_WITH(Type,name) \ public: Type& name() { assert(mut_.is_held()); return name##_; } \ private: Type name##_; Then we associate data with a mutex more easily: // Have to refer to the macro that was declared earlier struct MyData { GUARDED_WITH(mutex_type); GUARDED_MEMBER(vector<int>, v); GUARDED_MEMBER(Widget*, w); }
many latency race conditions automatically and deterministically at test time (vast improvement over intermittent timing-dependent customer bugs). MyData data1 = ..., data2 = ...; vector<int>* sneaky = nullptr; data1.v().push_back(10); // error: will assert data2.w()->ProcessYearEnd(); // error: will assert { // enter critical section lock_guard<MyData> hold(data1); // can treat it as a lockable object data1.v().push_back(10); // ok, locked data2.w()->ProcessYearEnd(); // error: will assert sneaky = &data1.v(); // ok, but avoid doing this } sneaky->push_back(10); // error, but won't assert Catches both "oops, forgot to lock" and "oops, took the wrong lock".
is defined as template<type T> (T source) // Additional template parameter M which is the mutex type template<typename M, typename T> constexpr void guarded(T source) { guarded_with<M>; // generates lock(), try_lock(), unlock() and the mutex of type M. // for every member variable for... (auto o : source.member_variables()) { // create an accessor and a private variable with a suffix name guarded_member(o.type(), o.name()); } // Checks at compile time that you're not guarding a class with no data members. compiler.require(source.member_functions().siz "a guarded class may not have member funct" "release (in the next release, we may supp" "synchronised functions)"); }; // User code: using a metaclass to write a type class(guarded<mutex_type>) MyData { vector<int> v; Widget* w; };
approach for how Qt moc could be implemented in terms of metaclass functions. The approach centers on writing metaclasses to encapsulate Qt conventions. In particular: Feature Qt moc style Proposed Qt class : public QObject QClass metaclass Q_OBJECT macro Signals and slots signals: access specifier qt::signal type
it’s easy to provide a default (as do C# and other languages), and a simple signal (outbound event notification) and slot (inbound event notification): Qt moc style: class MyClass : public QObject { Q_OBJECT public: MyClass(QObject* parent = nullptr); Q_PROPERTY(int value READ get_value WRITE set_value) int get_value() const { return value; } void set_value(int v) { value = v; } private: int value; signals: void mySignal(); public slots: void mySlot(); };
constexpr : std::vector , std::string , std::map ? Language features required by those. <experimental/reflect> (syntax TBD). C++ 23 Even more constexpr . Some code injection mechanism. <reflect> based on constexpr .