c++ qt visitor visitor-pattern qvariant

c++ - Patrón Visitor de QVariant(sin pruebas de tipo manual y de fundición)



qt visitor-pattern (1)

¿La clase QVariant de Qt tiene alguna implementación de patrón de visitante existente (y conveniente)?

Si no es así, ¿es posible lograr algo similar a boost::apply_visitor() , es decir, minimizar la duplicación en lo que respecta a probar el tipo y el casting?

Quiero lograr algo en las siguientes líneas:

/* 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());

Edición 1: QVariant::canConvert<T>() puede no ser la mejor solución anterior, pero el punto es: ¿se puede lograr automáticamente la asignación de tipo (entre QMetaType y typename T )?

Edición 2: "Functor de visitante" o "Función de visitante" realmente no me importa. Lo que importa es que quiero evitar probar el tipo y el lanzamiento (si es posible).


Visitante introspectivo

Podría aprovechar la información de introspección generada por moc. Declara que tu visitante es Q_GADGET . Esto agrega un único miembro estático estático de staticMetaObject al visitante, que contiene la información sobre los métodos invocables allí.

// https://github.com/KubaO/n/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 tiene toda la información necesaria para pasar tipos opacos como argumentos a métodos invocables:

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

Tal vez esto es lo que estabas buscando:

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

Esto concluye el ejemplo.

Visitante no introspectible

Puede factorizar la conversión a un tipo y ejecución de código condicional:

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

La función withConversion deduce el tipo de argumento del invocable e invoca al invocable si la variante es del tipo correspondiente:

#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; }

Consulte esta pregunta para obtener más información sobre la deducción del tipo de argumento en callables.