Проверьте, объявлен ли тип как система мета-типа (для SFINAE)

Чтобы разделить регистр для параметра t типа t с помощью SFINAE, я хочу знать, если оператор

QVariant::fromValue(t);

и/или

QVariant::value<T>();

компилирует. Если один компилируется, другой делает тоже, если вы не взломаете систему мета-типа. Они компилируются, если и только если t было объявлено с помощью Q_DECLARE_METATYPE(T).

Очень простой пример использования, когда вы хотите напечатать тип значения, просто qDebugging эквивалентный вариант, если и только если поддерживается системой мета-типа (мне это не нужно, но это показывает проблему в минимальном примере):

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>();
}

Я знаю несколько (но похожих) возможностей для этого, но все они вводят некоторые вспомогательные структуры, сложные enable_if и т.д. Теперь я знаю, что есть QTypeInfo, который, я думаю, уже предоставляет нечто вроде "объявлен" в системе типа Qt типа "тип". Однако этот класс не документирован, и поэтому он не предлагается использовать в долгосрочном и продуктивном коде, поскольку он может меняться между версиями Qt.

Есть ли простой способ (проще, чем с помощью "checker" + enable_if) проверять специализацию SFINAE, если тип t поддерживается QVariant?

Обратите внимание, что решение по-прежнему должно быть переносимым между различными версиями Qt (Qt4 и Qt5 могут использовать другое определение QTypeInfo). Однако я использую С++ 11, поэтому у меня есть доступ к std::enable_if, например.

"Не переносимым" способом является использование внутреннего определения QMetaTypeId2<T>::Defined в enable_if (это значение перечисления, определяемое как 0 или 1). Таким образом, рабочим решением будет:

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>();
}

Однако, поскольку QMetaTypeId2 не документирован и только внутренний материал, он не должен появляться в клиентском коде.

Ответы

Ответ 1

Вы должны объявить обертку, которая поможет вам. Лучшее, что я могу представить, это иметь несколько определений, основанных на version 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
    };
};

Это не эстетическое, но это функциональное, и в вашем коде вы можете использовать is_registered<T>::value, не беспокоясь о версии Qt. Кроме того, у меня нет Qt5 на данный момент, поэтому я не могу сказать вам, является ли QMetaTypeId2<T>::Defined правильным для него (хотя я думаю, что это так).


Невозможно использовать qMetaTypeId<T>(), чтобы проверить, зарегистрирован ли тип. На самом деле выражение qMetaTypeId<T>() всегда действует независимо от типа. Если он не зарегистрирован, тело функции не будет компилироваться (точнее: в Qt 4 и 5 (на данный момент), qMetaTypeId<T>() вызывает только другую функцию, которая не компилируется, если тип не зарегистрирован., вы не можете использовать SFINAE для его проверки. В результате код leemes дал в своем (теперь удаленном) ответе не будет работать как ожидалось.

Код был:

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))
{
};

Почему это не сработает? Цель состояла в том, что, поскольку вызов qMetaTypeId<T>() в незарегистрированном типе приводит к ошибке компиляции, "SFINAE исключает первую функцию, если тип не зарегистрирован". Проблема здесь в том, что qMetaTypeId<T>() всегда является допустимым выражением, поэтому qMetaTypeId<T>(), std::true_type() тоже, и decltype(qMetaTypeId<T>(), std::true_type()) отлично определен (со значением std::true_type).
Это связано с тем, что ошибка компиляции qMetaTypeId<T>() возникает в теле функции, а не в ее прототипе (к примеру, код будет компилироваться только в том случае, если функция в decltype объявлена ​​и правильно вызвана, то есть без аргументов шаблона для например, функция без шаблона).
Таким образом, поскольку эта перегрузка test() более конкретна, чем вариационная, она всегда выбирается, поэтому она всегда будет "возвращать", что тип зарегистрирован; вы можете увидеть его в следующем тестовом коде:

// ----------------------------------------------------------
// 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);
}

Вы можете захотеть узнать больше о SFINAE. Кроме того, вы можете прочитать qmetatype.h здесь.