Check if type is declared as a meta type system &#

2019-06-18 21:41发布

问题:

To make a case distinction for a parameter t of type T using SFINAE, I want to know if the statement

QVariant::fromValue(t);

and / or

QVariant::value<T>();

compiles. If the one compiles, the other one does too, unless you hack the meta type system. They compile if and only if T has been declared using Q_DECLARE_METATYPE(T).

Very simple usage example, where one wants to print the type of a value by simply qDebugging a variant-wrapped equivalent, if and only if supported by the meta type system (I don't need this, but this shows the problem in a minimal example):

template<class T>  // enable if T NOT registered in the Qt meta type system
void print(const T &t) {
    qDebug() << t;
}

template<class T>  // enable if T registered in the Qt meta type system
void print(const T &t) {
    qDebug() << QVariant::fromValue<T>();
}

I know several (yet similar) possibilities to do so, but all of them introduce some helper structs, complicated enable_if etc. Now I know that there is QTypeInfo which, I guess, already provides something like an "is declared in the Qt meta type system" type trait. However, this class is not documented and thus it's not suggested to be used in long term and productive code, as it might change between Qt versions.

Is there a very simple way (simpler than with a "checker" + enable_if) to check in a SFINAE specialization if a type T is supported by QVariant?

Please note that the solution still should be portable between different Qt versions (Qt4 and Qt5 might use a different QTypeInfo definition). However, I use C++11, so I have access to std::enable_if for example.

The "non-portable" way is to use the internal definition of QMetaTypeId2<T>::Defined in an enable_if (it's an enum value defined as either 0 or 1). Thus, a working solution would be:

template<class T>
typename std::enable_if<!QMetaTypeId2<T>::Defined>::type
print(const T &t) {
    qDebug() << t;
}

template<class T>
typename std::enable_if<QMetaTypeId2<T>::Defined>::type
print(const T &t) {
    qDebug() << QVariant::fromValue<T>();
}

However, since QMetaTypeId2 is not documented and only internal stuff, it should not appear in client code.

回答1:

You should declare a wrapper that will help you. The best I can think is to have several definitions, based on the version of Qt:

template<typename T>
struct is_registered
{
    enum
    {
        value = 
#if QT_VERSION >= 0x050000 // Qt 5.0.0
            QMetaTypeId2<T>::Defined
#elif QT_VERSION >= 0x040000 // Qt 4.0.0
            QMetaTypeId2<T>::Defined
#endif
    };
};

That's not aesthetic, but that is functionnal, and in your code you can use is_registered<T>::value without having to worry about the version of Qt. Also, I don't have Qt5 for the moment, so I cannot tell you whether QMetaTypeId2<T>::Defined is correct for it (although I think it is).


It is impossible to use qMetaTypeId<T>() to check if a type was registered. In fact, the expression qMetaTypeId<T>() is always valid, no matter the type. If it is not registered, the body of the function will not compile (to be more precise: in Qt 4 and 5 (for the moment), qMetaTypeId<T>() only calls another function that does not compile if the type is not registered. Thus, you cannot use SFINAE to test it. As a result, the code leemes gave in his (now deleted) answer will not work as expected.

The code was:

struct _test_is_declared_metatype
{
    template<class T>
    static auto test(T* t) -> decltype(qMetaTypeId<T>(), std::true_type());

    static std::false_type test(...);
};

template<class T>
struct is_declared_metatype : decltype(_test_is_declared_metatype::test<T>(0))
{
};

Why this won't work ? The intention was that because calling qMetaTypeId<T>() on a non-registered type results in a compile error, "SFINAE would exclude the first function when the type is not registered". The problem here is that qMetaTypeId<T>() is always a valid expression, so qMetaTypeId<T>(), std::true_type() is too, and decltype(qMetaTypeId<T>(), std::true_type()) is perfectly defined (with the value std::true_type).
This because the compilation error of qMetaTypeId<T>() stems in the body of the function, not in its prototype (by the way the code will compile only if the function in the decltype is declared and correctly called, ie no template arguments for a non-template function for example).
Thus, because this overload of test() is more specific than the variadic one, it will always be choosed, hence it will always 'return' that the type is registered; you can see it in the following test code:

// ----------------------------------------------------------
// qmetatype.h simplification -------------------------------
// ----------------------------------------------------------

template<typename T>
struct metatype
{
 enum { defined = 0 };
};

template<typename T>
struct metatype2
{
 enum { defined = metatype<T>::defined };
 static inline int id() { return metatype<T>::id(); }
};

template <typename T>
inline int metatypeId(
    T * /* dummy */ = 0
)
{
    return metatype2<T>::id();
}

#define register_meta_type( _type_ )  \
 template<>                           \
 struct metatype< _type_ >            \
 {                                    \
  enum { defined = 1 };               \
  static int id()                     \
  {                                   \
   /* Run-time registration in Qt */  \
   return __COUNTER__;                \
  };                                  \
 };



// ----------------------------------------------------------
// ----------------------------------------------------------
// ----------------------------------------------------------

class TestA {};
register_meta_type(TestA)

class TestB {};

class TestC {};
register_meta_type(TestC)

class TestD {};


#include <type_traits>

struct _test_is_declared_metatype
{
 /*
   metatypeId<T>() is always a valid expression. So this overload is
   always taken
 */
    template<class T>
    static auto test(T* t) -> decltype(metatypeId<T>(), std::true_type());

    static std::false_type test(...);
};

template<class T>
struct is_declared_metatype : decltype(_test_is_declared_metatype::test<T>(0))
{
};

#include <iostream>
#define PRINT_DEF( _type_ )  std::cout << #_type_ << " registered ? " << is_declared_metatype< _type_ >::value << "\n";
int main()
{
 std::cout << std::boolalpha;
 PRINT_DEF(TestA);
 PRINT_DEF(TestB);
 PRINT_DEF(TestC);
 PRINT_DEF(TestD);
}

You might want to read more about SFINAE. Also, you can read qmetatype.h here.