Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
392 views
in Technique[技术] by (71.8m points)

c++ - QVariant's Visitor pattern (without manual type testing and casting)

Does Qt's QVariant class has any existing (and convenient) Visitor pattern implementation?

If not, is it possible to achieve something similar to boost::apply_visitor(), i.e. minimize the the duplication in regard to testing the type and casting?

I want to achieve something along the following lines:

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

Edit 1: QVariant::canConvert<T>() may not be the best solution above, but the point is: can the type mapping (between QMetaType and typename T) be achieved automatically?

Edit 2: "Visitor functor" or "Visitor function" doesn't really matter for me. What matters is I want to avoid testing the type and casting (if at all possible).

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Introspectable Visitor

You could leverage the introspection information generated by moc. Declare your visitor to be Q_GADGET. This adds a single static staticMetaObject member to the visitor, containing the information about the invokable methods there.

// 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 has all the information necessary to pass opaque types around as arguments to invokable methods:

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

Perhaps this is then what you were after:

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

#include "main.moc"

This concludes the example.

Non-Introspectable Visitor

You can factor out the conversion to a type and conditional code execution:

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

The withConversion function deduces the argument type of the callable and invokes the callable if the variant is of the matching type:

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

See this question for more about argument type deduction in callables.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...