|
|
boost::variantSample programs |
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"
#include "boost/apply_visitor.hpp"
#include "boost/static_visitor.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;
}
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/apply_visitor.hpp"
#include "boost/static_visitor.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;
}
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 weight of
all star objects:
//
// 'classic' inheritance-based implementation
//
#include <vector>
#include <algorithm>
#include <iostream>
struct space_object
{
virtual int weight() const = 0;
virtual ~space_object() { }
};
struct space_ship : space_object
{
space_ship(int w = 0) : w_(w) { }
int weight() const { return w_; }
int get_speed() const { return 15; }
int w_;
};
struct star : space_object
{
star(int w = 0) : w_(w) { }
int weight() const { return w_; }
int w_;
};
struct total_weight
{
total_weight() : total_(0) { }
void operator()(space_object* so_p)
{
if(dynamic_cast<star*>(so_p))
total_ += so_p->weight();
}
int total_;
};
int main(int, char* [] )
{
typedef std::vector<space_object*> main_vec;
main_vec space_objects;
//fill space_objects
// ...
total_weight tw_job;
int total = std::for_each(space_objects.begin(), space_objects.end(),
tw_job).total_;
std::cout << "Total weight 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:
total_weight::operator() is a
costly operation. Alternatively, one can define an enum type
which will be used to correctly identify the concrete object, but this is an
error prone technique: the author must manually set the correct value for each
new concrete class. total_weight is unsafe when new classes are introduced.
Suppose a new class, black_hole - inherits directly from
space_object - is added to the code. total_weight will
silently ignore this class, possibly creating a havoc of run-time problems.
This is a major flaw from software engineering standpoint. 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"
#include "boost/apply_visitor.hpp"
#include "boost/static_visitor.hpp"
struct space_ship
{
space_ship(int w = 0) : w_(w) { }
int weight() const { return w_; }
int get_speed() const { return 15; }
int w_;
};
struct star
{
star(int w = 0) : w_(w) { }
int weight() const { return w_; }
int w_;
};
struct total_weight : boost::static_visitor<void>
{
total_weight() : total_(0) { }
void operator()(const star& a_star)
{
total_ += a_star.weight();
}
//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_weight tw_job;
std::for_each(space_objects.begin(), space_objects.end(),
boost::apply_visitor(tw_job));
std::cout << "Total weight of all stars = " << tw_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_weight 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.