Is there a way to convert a base type stored in QVariant without specializing the cast?

Viewed 1517

Let's consider this example:

QVariant v1(1);
QVariant v2("goofy");
QVariantList list;

list << v1 << v2;

for (const auto& var : list) {
   qdebug() << var;

   // nasty part
   if (var.type == QVariant::Int) {
      int value = var.toInt();

      // do something
   } else if (var.type == QVariant::QString) {
      QString value = var.toString();

      // do something
   }
}

The debug function shows the internal storage type of QVariant:

QVariant(int, 1) QVariant(QString, "goofy") 

Is there a way to avoid the ifs and do an explicit cast in order to access the internal type? More specifically, to get the value I would like to be able to write something like this:

auto value = var.ToData();

Edit: Since QVariant can hold a lot of type and you can even add custom types on it, it would be enough restricting the problem only to base types (int, double, bool, string)

2 Answers

No, this is not possible. As any other variant, QVariant basically acts as a union. Unless you know the type of the data, you can't get it. If your imaginary code auto value = var.ToData(); should work, the type of value would have to be resolvable at compile time - but then it wouldn't be a variant at all.

The whole point of variant is that it enables you to store multiple types in a single value, all done in runtime. Internally, it stores the type of it's value, but it's a runtime value - so if you don't know the type exactly, there is no other way than to make a long switch.

It is possible, but at a price - and QVariant doesn't do it. There are two related approaches:

  1. QVariant could dispatch all operators to the type-specific ones at runtime, e.g. adding two QVariants that hold integers could invoke int operator+(int,int). It's not done because it is hard to do it in a sufficiently general way, but if you had a few types in mind - you certainly could make your own type that does it. It could, internally, use QVariant, but I'd be leery of exposing it indiscriminately as a QVariant. It could be convertible to a QVariant, though, so that you could pass the type where a variant is expected, but not have it itself be a variant - i.e. use composition, not inheritance.

  2. QVariant could implement templated type forwarding that would deduce the type based on what operations you perform on it, using SFINAE and template expressions, and replace the operations on QVariant with operations on the contained type, with no runtime overhead. Writing out a sufficiently specific expression would deduce the type, and if it wasn't specific enough, then you'd have to provide type hints - just like in any functional language with type deduction.

When you have a small list of types in mind, either approach could work very well, but with the plethora of types QVariant has to support - including a-priori unknown user types - the general approach to have so many limitations as to be unusable. It's hard to tell without knowing what you do with those values. More could be said about it when given less laconic of a description than //do something.

Related