Unterschiedliche Datentypen mit boost::variant

In C++ gibt es Fälle, in denen eine Funktion Werte zurückliefern soll, die nicht alle denselben Datentyp besitzen. Ein Beispiel hierfür ist eine Funktion, die einen JSON-String parsen soll und den deserialisierten Wert zurückgeben soll:

<Datentyp?> JsonDeserialize(const std::string& json);

Wenn wir die Funktion in C schreiben würden, könnten wir uns mit einem einfachen Trick behelfen: wir verwenden ein “Tagged Union” – also ein Union, zu dem wir uns zusätzlich merken, welches Feld darin aktuell gültig ist:

typedef struct {
  int tag; /* Das aktuell gültige Feld in dem Union */
  union {
    double value_double;
    char *value_string;
    ...
  };
} my_variant;

Mit C++ würde dies allerdings nicht funktionieren, da in einem Union nur POD-Typen erlaubt sind – also Datentypen, die keine Konstruktoren, Destruktoren oder andere Methoden enthalten. Der Hintergrund dabei ist, dass C++ nicht entscheiden kann, welchen Konstruktor und Destruktor es für das Union aufrufen soll, da es nicht weiss, welches Feld gültig ist.
Die Boost-Library bietet für dieses Problem eine elegante Lösung an: das boost::variant-Template kann verwendet werden, um eigene Typen zu definieren, die sich wie unser my_variant-Struct verhalten, aber zusätzlich auch nicht-triviale Typen (mit Konstruktor, Destruktor, usw.) enthalten kann:

typedef boost::variant<double, std::string, ...> my_variant;

Der my_variant-Typ verfügt dabei für jeden angegebenen Template-Parameter über einen passenden Konstruktor, der eine einfache Zuweisung erlaubt:

my_variant val = 7;

In diesem Fall wird der interne Typ des Variants auf “double” gesetzt und der Wert 7 gespeichert.
Um zu prüfen, welchen Typ ein Variant-Wert hat, kann die Methode “which” verwendet werden. Sie liefert einen null-basierten Index in die Template-Parameter zurück. Mit boost::get kann der Wert eines Variants extrahiert werden:

double dval = boost::get<double>(val);

Alternativ (und von der Boost-Dokumentation vorgeschlagen) kann auch das Visitor-Pattern verwendet werden, um auf die eigentlichen Werte zuzugreifen. Mehr Informationen gibt es in der Boost-Dokumentation.