C++ Boost

boost::variant

Sample programs



A quick example

This program computes the total sum of numerical values of various types.

The code defines a variant object, v1, which can hold a value of this set of types: short, int, float, and double.

double_sum is a visitor class: Its function-call operator, double_sum::operator(), accepts a single value, and adds it to the total_ data member. The program uses the construct: "boost::apply_visitor(visitor, variant);" to invoke the visitor object, (ds) on the specified variant (v1).

Naturally, ds.total_ holds the total of all previously "visited" values, and - therefore - at the bottom of main(), its value is: 5 + 16 + 3.11 + 15.3 = 39.41.

#include <iostream>
#include "boost/variant.hpp"

struct double_sum : boost::static_visitor<double>
{
   double_sum() : total_(0.0) { }

   template<class X>
   double operator()(X x)
   {
      total_ += x;
      return total_;
   }

   double total_;
};

int main(int, char* [] )
{
   double_sum ds;
   boost::variant<short, int, float, double> v1;

   v1 = short(5);
   boost::apply_visitor(ds, v1);  // Apply ds to v1 (1st time)

   v1 = 16;
   boost::apply_visitor(ds, v1);  // 2nd time

   v1 = 3.11f;
   boost::apply_visitor(ds, v1);  // 3rd

   v1 = 15.3;
   double total = boost::apply_visitor(ds, v1);  // 4th

   // Expected output: "Total = 39.41"
   std::cout << "Total = " << total << std::endl;

   return 0;
}

A binary tree implementation

This sample program shows how incomplete<T> can be used to define recursive variants.
The code creates a small binary tree and then performs an in-order walk thru its nodes, producing this output: 3 4 6 10 19 20 23

#include <iostream>

#include "boost/variant.hpp"
#include "boost/incomplete.hpp"


using boost::variant;
using boost::incomplete;
using std::cout;
using std::endl;


struct non_leaf_node; // Forward declaration

// Define a variant with these two types:
//   1) int
//   2) The (incomplete) non_leaf_node struct
typedef variant<int, incomplete<non_leaf_node> > tree_node;

struct non_leaf_node
{
   non_leaf_node(const tree_node& l, int num, const tree_node& r)
      :  left_(l), right_(r), num_(num) { }
   non_leaf_node(const non_leaf_node& other)
      :  left_(other.left_), right_(other.right_), num_(other.num_)  { }

   int num_;
   tree_node left_;
   tree_node right_;
};


struct tree_printer : boost::static_visitor<void>
{
   void operator()(int n) const
   {
      cout << n << ' ';
   }

   void operator()(const non_leaf_node& node) const
   {
      boost::apply_visitor(*this, node.left_);
      cout << node.num_ << ' ';
      boost::apply_visitor(*this, node.right_);
   }
};


int main(int, char* [] )
{
   //Build a binary search tree:
   non_leaf_node a(3,4, 6);
   non_leaf_node b(19, 20, 23);
   non_leaf_node c(a,10, b);

   tree_node root(c);

   //Perform an in-order walk
   boost::apply_visitor(tree_printer(), root);

   return 0;
}

Polymorphism: Inheritance vs. Variants

Let's assume we need to write a program that manipulates instances of star and space_ship, where each of these two classes inherits from space_object. The program maintains a vector of pointers to these objects, which is used to calculate the total mass of all star objects:

   //
   //  'classic' inheritance-based implementation
   //
   #include <vector>
   #include <algorithm>
   #include <iostream>

   struct space_object
   {
      virtual int mass() const = 0;
      virtual ~space_object() { }
   };

   struct space_ship : space_object
   {
      space_ship(int m = 0) : m_(m) { }
      int mass() const { return m_; }
      int get_speed() const { return 15; }

      int m_;
   };

   struct star : space_object
   {
      star(int m = 0) : m_(m) { }
      int mass() const { return m_; }

      int m_;
   };

   struct total_mass
   {
      total_mass() : total_(0) { }

      void operator()(space_object* so_p)
      {
         if(dynamic_cast<star*>(so_p))
            total_ += so_p->mass();
      }

      int total_;
   };

   int main(int, char* [] )
   {
      typedef std::vector<space_object*> main_vec;
      main_vec space_objects;

      //fill space_objects
      // ...

      total_mass tm_job;
      int total = std::for_each(space_objects.begin(), space_objects.end(),
         tm_job).total_;

      std::cout << "Total mass of all stars = " << total
         << std::endl;

      //Apply delete to all pointers stored in space_objects
      // ...

      return 0;
   }

The are several issues worth noticing about this sample:

This real-life design problem can be elegantly solved using variants. Here is the variant-based code:

   //
   //  Variant-based implementation
   //
   #include <vector>
   #include <algorithm>
   #include <iostream>

   #include "boost/variant.hpp"

   struct space_ship
   {
      space_ship(int m = 0) : m_(m) { }
      int mass() const { return m_; }
      int get_speed() const { return 15; }

      int m_;
   };

   struct star
   {
      star(int m = 0) : m_(m) { }
      int mass() const { return m_; }

      int m_;
   };

   struct total_mass : boost::static_visitor<void>
   {
      total_mass() : total_(0) { }

      void operator()(const star& a_star)
      {
         total_ += a_star.mass();
      }

      //space_ship objects are ignored:
      void operator()(const space_ship& ) { }

      int total_;
   };

   int main(int, char* [] )
   {
      typedef boost::variant<star, space_ship> space_var;
      typedef std::vector<space_var> main_vec;
      main_vec space_objects;

      //fill space_objects
      // ...

      total_mass tm_job;
      std::for_each(space_objects.begin(), space_objects.end(),
         boost::apply_visitor(tm_job));

      std::cout << "Total mass of all stars = " << tm_job.total_
         << std::endl;

      return 0;
   }

This implementation directly addresses the three issues raised by the non-variant implementation: (1) The space_objects vector now holds objects (rather than pointers), (2) dynamic_cast<>s are not needed at all, and - most importantly - (3) the compiler will produce an error if total_mass is not changed, when a new class is introduced.


Revised 14 February 2003

© Copyright Eric Friedman and Itay Maman 2002-2003. All rights reserved.

Permission to use, copy, modify, distribute and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation. Eric Friedman and Itay Maman make no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty.