Шаблон посетителя QVariant (без ручного тестирования типов и приведения)

Есть ли в классе QVariant Qt существующая (и удобная) реализация шаблона Visitor?

Если нет, то возможно ли достичь чего-то похожего на boost::apply_visitor()минимизировать дублирование при тестировании типа и приведения?

Я хочу достичь чего-то следующего:

/* I have a QVariant that can contain anything, including user types */
QVariant variant;

/* But in my Visitor I'm interested only in ints and QStrings (for the sake of the example) */
struct Visitor
{
void operator()(int i) { /* do something with int */ }
void operator()(QString s) { /* ...or QString */ }
};

/* The question is: */
/* Can this be implemented in a generic way (without resorting to particular template parameters)? */
template <typename VisitorT>
void visit(QVariant variant, VisitorT visitor)
{
if (variant.canConvert<int>()) {
visitor(variant.value<int>());
} else if (variant.canConvert<QString>()) {
visitor(variant.value<QString>());
} /* and so on (if needed for other types)... */
}

/* So that later I can use it like that */
visit(variant, Visitor());

Изменить 1: QVariant::canConvert<T>() может быть не лучшим решением выше, но дело в том, может ли отображение типа (между QMetaType а также typename T) быть достигнуто автоматически?

Правка 2: «Функтор посетителя» или «Функция посетителя» для меня не имеет значения. Важно то, что я хочу избегать тестирования типа и кастинга (если это вообще возможно).

2

Решение

Интроспективный посетитель

Вы можете использовать информацию для самоанализа, сгенерированную moc. Объявите своего посетителя как Q_GADGET, Это добавляет одну статическую staticMetaObject член для посетителя, содержащий информацию о вызываемых там методах.

// https://github.com/KubaO/stackoverflown/tree/master/questions/variant-convert-38071414
#include <QtCore>

struct Foo {
int a;
Foo() = default;
explicit Foo(int a) : a(a) {}
};
QDebug operator<<(QDebug debug, const Foo & f) {
return debug << f.a;
}
Q_DECLARE_METATYPE(Foo)

struct Visitor
{
Q_GADGET
Q_INVOKABLE void visit(int i) { qDebug() << "got int" << i; }
Q_INVOKABLE void visit(const QString & s) { qDebug() << "got string" << s; }
Q_INVOKABLE void visit(const Foo & f) { qDebug() << "got foo" << f; }
};

Qt имеет всю информацию, необходимую для передачи непрозрачных типов в качестве аргументов вызываемым методам:

template <typename V>
bool visit(const QVariant & variant, const V & visitor) {
auto & metaObject = V::staticMetaObject;
for (int i = 0; i < metaObject.methodCount(); ++i) {
auto method = metaObject.method(i);
if (method.parameterCount() != 1)
continue;
auto arg0Type = method.parameterType(0);
if (variant.type() != (QVariant::Type)arg0Type)
continue;
QGenericArgument arg0{variant.typeName(), variant.constData()};
if (method.invokeOnGadget((void*)&visitor, arg0))
return true;
}
return false;
}

Возможно, это то, что вы были после:

int main() {
visit(QVariant{1}, Visitor{});
visit(QVariant{QStringLiteral("foo")}, Visitor{});
visit(QVariant::fromValue(Foo{10}), Visitor{});
}

#include "main.moc"

Это завершает пример.

Не интроспективный посетитель

Вы можете выделить преобразование в тип и выполнение условного кода:

void visitor(const QVariant & val) {
withConversion(val, [](int v){
qDebug() << "got an int" << v;
})
|| withConversion(val, [](const QString & s){
qDebug() << "got a string" << s;
});
}

int main() {
visitor(QVariant{1});
visitor(QVariant{QStringLiteral("foo")});
}

withConversion Функция выводит тип аргумента вызываемого и вызывает вызываемый, если вариант имеет соответствующий тип:

#include <QtCore>
#include <type_traits>

template <typename T>
struct func_traits : public func_traits<decltype(&T::operator())> {};

template <typename C, typename Ret, typename... Args>
struct func_traits<Ret(C::*)(Args...) const> {
using result_type = Ret;
template <std::size_t i>
struct arg {
using type = typename std::tuple_element<i, std::tuple<Args...>>::type;
};
};

template <typename F> bool withConversion(const QVariant & val, F && fun) {
using traits = func_traits<typename std::decay<F>::type>;
using arg0_t = typename std::decay<typename traits::template arg<0>::type>::type;
if (val.type() == (QVariant::Type)qMetaTypeId<arg0_t>()) {
fun(val.value<arg0_t>());
return true;
}
return false;
}

Увидеть этот вопрос для больше о выводе типа аргумента в вызываемых.

5

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]