I'm writing a lexical scanner that generates a stream of tokens from some input. Those tokens have a type and a value. Since I'm using Qt I chose to store the token data as a QVariant
. This works pretty well for token data that is of a non-custom type.
Unfortunately, I have several custom types that are stored inside of tokens as well. The tokens have a toString()
function that outputs a token description (for debugging), but for all tokens that have data of a custom type this function gives an empty string. The code goes like this:
Test.h:
struct Test
{
QString value_;
Test(const QString& value = "");
QString toString();
};
Q_DECLARE_METATYPE(Test)
Token.h:
struct Token
{
TokenType type_;
QVariant value_;
...
virtual QString toString() const;
};
Token.cpp:
QString Token::toString() const
{
QStringList sl;
sl << "Token(" << ::toString(type_) << ", ";
sl << value_.toString() << ")";
return sl.join("");
}
Example output from scanner:
"Token(TT_TEST, )"
"Token(TT_PLUS, +)"
"Token(TT_NUMBER, 5)"
"Token(TT_end, #)"
The TT_TEST
token contains a Test class and I would expect the variant to print it's value. Unfortunately this does not work, and I've tried a lot of solutions that did not work. My current workaround looks like this:
template <typename T>
bool writeToStringList(QStringList& sl, QVariant v)
{
if (!v.canConvert<T>()) return false;
sl << v.value<T>().toString();
return true;
}
and a modified toString()
function:
sl << "Token(";
sl << ::toString(type_) << ", ";
if (!writeToStringList<Test>(sl, value_)) {
sl << value_.toString();
}
and I have to do this for all my custom types which just feels pretty clumsy and wrong.
I figure there must be a better solution to this problem. Can anyone of you:
QVariant
in a better way, orQVariant
. (I had a template solution earlier but I ran into different problems there, so I would need an example if that is suggested).?
Q_DECLARE_METATYPE() is in fact sufficient to enable aggregation of a custom type in a QVariant. This does not cover aspects like implicit type conversion and comparison in context of the QVariant though. Qt5 assumed, to facilitate implicit conversion to QString you may do the following:
#include <QMetaType>
struct Token {
QString _value;
};
Q_DECLARE_METATYPE( Token* );
QString tokenToString( Token* t ) {
return t->_value );
}
int main(int argc, char* argv[]) {
QMetaType::registerConverter<Token*,QString>( tokenToString );
Token t = { QString("hello") };
QVariant value;
value.setValue( &t );
std::cout << value << std::endl;
}
This is of course also possible (and more save) with Q_DECLARE_METATYPE( MyType )
and directly aggregating a Token instance in the QVariant instead of a pointer to Token.
See also this post from the Qt forum
You need to register a QString converter to the meta-object system for the custom type Token
To do that, you have two ways:
Then you can directly register this method as converter
#include <QDebug>
#include <QMetaType>
#include <functional>
struct Token
{
QString toString() const
{
return _value;
}
QString _value;
};
Q_DECLARE_METATYPE( Token )
int main(int argc, char* argv[])
{
qRegisterMetaType<Token>();
QMetaType::registerConverter(&Token::toString);
Token t {"hello"};
QVariant value;
value.setValue( t );
qDebug() << value.toString();
}
Then you can register this external toString function using unary function
#include <QDebug>
#include <QMetaType>
#include <functional>
struct Token
{
QString _value;
};
Q_DECLARE_METATYPE( Token )
QString tokenToString(const Token &t)
{
return t._value;
}
struct toQString : public std::unary_function<Token,QString>
{
QString operator() (const Token &value) const
{
return tokenToString(value);
}
};
int main(int argc, char* argv[])
{
qRegisterMetaType<Token>();
QMetaType::registerConverter<Token, QString, toQString>(toQString());
Token t {"hello"};
QVariant value;
value.setValue( t );
qDebug() << value.toString();
}
PS: If you want to do
qDebug() << value;
you need to implement the QDebug operator in your custom Type, and register this operator for the custom type Token to the meta-object system.
QMetaType::registerDebugStreamOperator<Token>()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With