Usage


To start using variants in your programs, include the header "boost/variant.hpp". This header includes the entire library, so you don't need to know which individual features to use; later, you may want to reduce the dependencies by only including the relevant files for the problem at hand. When declaring a variant type, we must define the set of types that it will be capable of storing. The most common way to accomplish this is using template arguments. A variant that is capable of holding a value of type int, std::string, or double is declared like this.

 boost::variant<int,std::string,double> my_first_variant; 

When the variable my_first_variant is created, it ends up containing a default-constructed int, because int is first among the types that the variant can contain. We can also pass a value that is convertible to one of those types to initialize the variant.

 boost::variant<int,std::string,double>   my_first_variant("Hello world"); 

At any give time, we can assign a new value, and as long as the new value is unambiguously and implicitly convertible to one of the types that the variant can contain, it works perfectly.

 my_first_variant=24; my_first_variant=2.52; my_first_variant="Fabulous!"; my_first_variant=0; 

After the first assignment, the contained value is of type int; after the second, it's a double; after the third, it's a std::string; and then finally, it's back to an int. If we want to see that this is the case, we can retrieve the value using the function boost::get, like so:

 assert(boost::get<int>(my_first_variant)==0); 

Note that if the call to get fails (which would happen if my_first_variant didn't contain a value of type int), an exception of type boost::bad_get is thrown. To avoid getting an exception upon failure, we can pass a pointer to a variant to get, in which case get returns a pointer to the value or, if the requested type doesn't match the type of the value in the variant, it returns the null pointer. Here's how it is used:

 int* val=boost::get<int>(&my_first_variant); assert(val && (*val)==0); 

The function get is a very direct way of accessing the contained valuein fact, it works just like any_cast does for boost::any. Note that the type must match exactly, including at least the same cv-qualification (const and volatile). However, a more restrictive cv-qualification will succeed. If the type doesn't match and a variant pointer is passed to get, the null pointer is returned. Otherwise, an exception of type bad_get is thrown.

 const int& i=boost::get<const int>(my_first_variant); 

Code that relies too heavily on get can quickly become fragile; if we don't know the type of the contained value, we might be tempted to test for all possible combinations, like the following example does.

 #include <iostream> #include <string> #include "boost/variant.hpp" template <typename V> void print(V& v) {   if (int* pi=boost::get<int>(&v))     std::cout << "It's an int: " << *pi << '\n';   else if (std::string* ps=boost::get<std::string>(&v))     std::cout << "It's a std::string: " << *ps << '\n';   else if (double* pd=boost::get<double>(&v))     std::cout << "It's a double: " << *pd << '\n';   std::cout << "My work here is done!\n"; } int main() {   boost::variant<int,std::string,double>     my_first_variant("Hello there!");   print(my_first_variant);   my_first_variant=12;   print(my_first_variant);   my_first_variant=1.1;   print(my_first_variant); } 

The function print does its job correctly now, but what if we decide to change the set of types for the variant? Then we will have introduced a subtle bug that won't be caught at compile time; the function print will not print the value of any other types than the ones we've originally anticipated. If we hadn't used a template function, but required an exact signature of a variant, we would risk proliferation of overloads to accommodate the same functionality for different types of variants. The next section discusses the concept of visiting variants, and the problem that (typesafe) visitation solves.

Visiting Variants

Let's start with an example that explains why using get isn't as robust as one would like. Starting with the code from the previous example, let's alter the types that the variant can contain, and call print with the char value for the variant, too.

 int main() {   boost::variant<int,std::string,double,char>     my_first_variant("Hello there!");      print(my_first_variant);   my_first_variant=12;   print(my_first_variant);   my_first_variant=1.1;   print(my_first_variant);   my_first_variant='a';   print(my_first_variant); } 

This compiles cleanly even though we have added char to the set of types that the variant can contain and the last two lines of the program set a char value and call print. (Note that print is parameterized on the variant type, so it adapts to the new variant definition easily.) Here's the output of running the program:

 It's a std::string: Hello there! My work here is done! It's an int: 12 My work here is done! It's a double: 1.1 My work here is done! My work here is done! 

There's a problem showing in that output. Notice that there is no value reported before the final, "My work here is done!" The reason is that as it stands, print doesn't output the value for any types other than those it was originally designed for (std::string, int, and double), yet it compiles and runs cleanly. The value of the variant is simply ignored if its current type isn't among those supported by print. There are more potential problems with using get, such as getting the order of the if-statements right for class hierarchies. Note that this doesn't mean you should avoid using get altogether; it just emphasizes that it's sometimes not the best solution. What would be better here is a mechanism that somehow allows us to state which types of values are acceptable, and have that statement be validated at compile time. This is exactly what the variant visitation mechanism does. By applying a visitor to a variant the compiler ensures that they are fully compatible. Such visitors in Boost.Variant are function objects with function call operators that accept arguments corresponding to the set of types that the variants they visit can contain.

Rewriting the now infamous function print as a visitor looks like this:

 class print_visitor : public boost::static_visitor<void> { public:   void operator()(int i) const {     std::cout << "It's an int: " << i << '\n';   }   void operator()(std::string s) const {     std::cout << "It's a std::string: " << s << '\n';   }   void operator()(double d) const {     std::cout << "It's a double: " << d << '\n';   } }; 

To make print_visitor a visitor for variants, we have it inherit publicly from boost::static_visitor to get the correct typedef (result_type), and to explicitly state that this class is a visitor type. The class implements three overloaded versions of the function call operator, which accept an int, a std::string, and a double, respectively. To visit a variant, one uses the function boost::apply_visitor(visitor, variant). If we replace the existing calls to print with calls to apply_visitor, we end up with something like this:

 int main() {   boost::variant<int,std::string,double,char>     my_first_variant("Hello there!");      print_visitor v;      boost::apply_visitor(v,my_first_variant);   my_first_variant=12;   boost::apply_visitor(v,my_first_variant);   my_first_variant=1.1;   boost::apply_visitor(v,my_first_variant);   my_first_variant='a';   boost::apply_visitor(v,my_first_variant); } 

Here, we create a print_visitor, named v, and apply it to my_first_ variant after putting each value in it. Because we don't have a function call operator accepting char, this code fails to compile, right? Wrong! A char can be unambiguously converted to an int, so the visitor is compatible with our variant type. This is what we get when running the program.

 It's a std::string: Hello there! It's an int: 12 It's a double: 1.1 It's an int: 97 

We learn two things from thisthe first is that the character a has the ASCII value 97, and the second, more important, lesson is that if a visitor accepts its arguments by value, any implicit conversions will be applied to the values being passed. If we want only the exact types to be compatible with the visitor (and also avoid copying the value from the variant), we must change how the visitor function call operators accept their arguments. The following version of print_visitor only works for the types int, std::string, and double; and any other types that provide an implicit conversion to a reference of one of those types.

 class print_visitor : public boost::static_visitor<void> { public:   void operator()(int& i) const {     std::cout << "It's an int: " << i << '\n';   }   void operator()(std::string& s) const {     std::cout << "It's a std::string: " << s << '\n';   }   void operator()(double& d) const {     std::cout << "It's a double: " << d << '\n';   } }; 

If we compile the example again, the compiler will be really upset, saying something like this:

 c:/boost_cvs/boost/boost/variant/variant.hpp: In member function `typename Visitor::result_type boost::detail:: variant:: invoke_visitor<Visitor>::internal_visit(T&, int) [with T = char, Visitor = print_visitor]': [Snipped lines of irrelevant information here] c:/boost_cvs/boost/boost/variant/variant.hpp:807: error: no match for call to `(print_visitor) (char&)' variant_sample1.cpp:40: error: candidates are:   void print_visitor::operator()(int&) const variant_sample1.cpp:44: error:   void print_visitor::operator()(std::string&) const variant_sample1.cpp:48: error:   void print_visitor::operator()(double&) const 

This error pinpoints the problem: There is no candidate function for char arguments! That's one important reason why typesafe compile time visitation is such a powerful mechanism. It makes the visitation robust with regard to types, and avoids the tedium of type-switching. Creating visitors is just as easy as creating other function objects, so the learning curve here isn't very steep. When the set of types in the variants may change (they tend to do that!), creating visitor classes is much more robust than relying solely on get. There is a higher initial cost, but it's typically worth it for non-trivial uses.

Generic Visitors

By using the visitor mechanism and a parameterized function call operator, it's possible to create generic visitors that are capable of accepting values of any type (that can syntactically and semantically handle whatever the generic function call operator implementation requires). This is very useful for treating disparate types uniformly. Typical examples of "universal" features are the C++ operators, such as arithmetic and IOStreams shift operators. The following example uses operator<< to print the variant values to a stream.

 #include <iostream> #include <sstream> #include <string> #include <sstream> #include "boost/variant.hpp" class stream_output_visitor :   public boost::static_visitor<void> {   std::ostream& os_; public:   stream_output_visitor(std::ostream& os) : os_(os) {}   template <typename T> void operator()(T& t) const {     os_ << t << '\n';   } }; int main() {   boost::variant<int,std::string> var;   var=100;   boost::apply_visitor(stream_output_visitor(std::cout),var);   var="One hundred";   boost::apply_visitor(stream_output_visitor(std::cout),var); } 

The idea is that the member function template for the function call operator in stream_output_visitor will be instantiated once for each type visited (int and std::string, in this case). Because std::cout << 100 and std::cout << std::string("One hundred") are both well defined, the code compiles and works flawlessly.

Of course, operators are just one example of what could be used in a generic visitor; they simply happen to apply to a great many types. When calling functions on the values, or passing them as arguments to other functions, the requirements are that the member function exists for all types being passed to the operator, and that there are suitable overloads for the functions being called. Another interesting aspect of this parameterized function call operator is specializing the behavior for some types, but still allowing a generic implementation to be available for the rest of the types. In other words, you create overloaded function call operators for some types and rely on the member function template for the rest. This is, in a sense, related to template specialization, where behavior is specialized based on type information.

Binary Visitors

The visitors that we've seen so far have all been unarythat is, they accept one variant as their sole argument. Binary visitors accept two (possibly different) variants. This concept is, among other things, useful for implementing relations between variants. As an example, we shall create a lexicographic sort order for variant types. To do so, we'll use an enormously useful component from the Standard Library: std::ostringstream. It will take anything OutputStreamable and, on demand, produce a std::string out of it. We can thus lexically compare fundamentally different variant types, assuming that all of the bound types support streaming. Just as with regular visitors, binary visitors should derive publicly from boost::static_visitor, and the template parameter denotes the return type of the function call operator(s). Because we are creating a predicate, the return type is bool. Here, then, is the binary predicate, which we shall put to use shortly.

 class lexicographical_visitor :   public boost::static_visitor<bool> { public:   template <typename LHS,typename RHS>     bool operator()(const LHS& lhs,const RHS& rhs) const {       return get_string(lhs)<get_string(rhs);     } private:   template <typename T> static std::string     get_string(const T& t) {     std::ostringstream s;     s << t;     return s.str();   }   static const std::string& get_string(const std::string& s) {      return s;   } }; 

The function call operator is parameterized on both of its arguments, which means that it accepts any combination of two types. The requirements for the set of possible types in the variants is that they be OutputStreamable. The member function template get_string uses a std::ostringstream to convert its argument to its string representationhence the OutputStreamable requirement. (To use std::ostringstream, remember to include the header <sstream>.) The member function get_string simply accounts for the fact that a value of type std::string is already of the required type and so it skips the trip through std::ostringstream and just returns its argument. After the two arguments have been converted to std::string, all that remains is to compare them, which we do using operator<. Now let's put this visitor to the test by sorting the elements of a container using its services (we'll also reuse the stream_output_visitor that we created earlier in this chapter).

 #include <iostream> #include <string> #include <vector> #include <algorithm> #include "boost/variant.hpp" int main() {   boost::variant<int,std::string> var1="100";   boost::variant<double> var2=99.99;   std::cout << "var1<var2: " <<     boost::apply_visitor(       lexicographical_visitor(),var1,var2) << '\n';   typedef std::vector<     boost::variant<int,std::string,double> > vec_type;   vec_type vec;   vec.push_back("Hello");   vec.push_back(12);   vec.push_back(1.12);   vec.push_back("0");   stream_output_visitor sv(std::cout);   std::for_each(vec.begin(),vec.end(),sv);   lexicographical_visitor lv;   std::sort(vec.begin(),vec.end(),boost::apply_visitor(lv));   std::cout << '\n';   std::for_each(vec.begin(),vec.end(),sv); }; 

First of all, we apply the visitor to two variants, var1 and var2, like so:

 boost::apply_visitor(lexicographical_visitor(),var1,var2) 

As you can see, the difference from the unary visitors is that two variants are passed to the function apply_visitor. A more common example of usage is to use the predicate for sorting the elements, which we do like this:

 lexicographical_visitor lv; std::sort(vec.begin(),vec.end(),boost::apply_visitor(lv)); 

When the sort algorithm is invoked, it compares its elements using the predicate that we pass to it, which is an instance of lexicographical_visitor. Note that boost::variant already defines operator<, so it's possible to simply sort the container without our predicate.

 std::sort(vec.begin(),vec.end()); 

But the default sort order, which first checks the current value index via which, arranges the elements in the order 12, 0, Hello, 1.12, and we wanted a lexicographical order. Because both operator< and operator== is provided for the variant class, variants can be used as the element type of all Standard Library containers. When these default relations aren't enough, implement the ones you need with binary visitors.

There Is More to Know

We haven't covered all of the functionality of the Boost.Variant library. The remaining advanced features are not needed as often as those we have explored. However, I'll mention them briefly, so you will at least know what's available should you find that you need them. The macro, BOOST_VARIANT_ENUM_PARAMS, is useful when overloading/specializing functions and class templates for variant types. The macro helps by enumerating the set of types that the variant can contain. There is support for creating variant types using type sequencesthat is, compile-time lists that denote the set of types for the variant, through make_variant_over. Recursive variant types, which are useful for creating expressions that are themselves variant types, are available using recursive_wrapper, make_recursive_variant, and make_recursive_variant_over. If you need these additional features, the online documentation does an excellent job of explaining them.



    Beyond the C++ Standard Library(c) An Introduction to Boost
    Beyond the C++ Standard Library: An Introduction to Boost
    ISBN: 0321133544
    EAN: 2147483647
    Year: 2006
    Pages: 125

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net