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.
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:
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 whetherQMetaTypeId2<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 expressionqMetaTypeId<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:
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 thatqMetaTypeId<T>()
is always a valid expression, soqMetaTypeId<T>(), std::true_type()
is too, anddecltype(qMetaTypeId<T>(), std::true_type())
is perfectly defined (with the valuestd::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 thedecltype
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:You might want to read more about SFINAE. Also, you can read
qmetatype.h
here.