The following example writes "Hello, World" to standard output using Boost.dynamic_any facilities:
#include <string> #include <iostream> #include <boost/mpl/list.hpp> #include <boost/dynamic_any/any.hpp> #include <boost/dynamic_any/operations.hpp> typedef boost::dynamic_any::any< boost::mpl::list< boost::dynamic_any::to_ostream > > printable_any; int main() { printable_any x(std::string("Hello")); std::cout << x; x = ','; std::cout << x; char & c = extract<char &>(x); c = ' '; std::cout << x; x = std::string("World!"); std::cout << x << '\n'; } |
Let's skip include directives for now and start from printable_any typedef. As it seen from the definition, printable_any is actually boost::dynamic_any::any class template that accepts MPL sequence as a template parameter. Let's refer to this template as an any class template or simply an any in the rest of this tutorial. The sequence which it accepts is a list of supported operations. In this example, only one operation is supported, namely boost::dynamic_any::to_ostream. As you may guess, it helps to print values held by any to ostream.
Now take a look at first two lines of main function:
printable_any x(std::string("Hello")); std::cout << x; |
At first line printable_any x is constructed and from this line it holds "Hello" string. At second line x is printed whatever it holds. Very simple and straightforward code, isn't it?
In next lines new value is assigned to x and then printed. Note that the held type is changed from std::string to char:
x = ','; std::cout << x; |
Ability to print held value is fine but it's also desirable to get access to the any content. The answer is yes, it's possible:
char & c = extract<char &>(x); c = ' '; std::cout << x; |
At first line we get a reference to held value using boost::dynamic_any::extract function. As we get non-const reference we are able to modify held value. It is done in second line of the code fragment. Third line writes modified value to standard output.
Last part of main function obviously prints the rest:
x = std::string("World!"); std::cout << x << '\n'; |
There are four header files in the library:
Header | Description |
#include <boost/dynamic_any/fwd.hpp> | Forward declarations of all public entities defined in the library. |
#include <boost/dynamic_any/any.hpp> | Definition of the any class template and related functions. |
#include <boost/dynamic_any/function.hpp> | Support for user-defined operations. |
#include <boost/dynamic_any/operations.hpp> | Standard operations such as plus, negate, less, to_ostream etc. |
The library has linear bottom-to-top include dependency graph. That is,
every header listed in the table included by its neighbour below.
Construction and assignment
Among different instantiations of any class template one is "minimal". It's boost::dynamic_any::any<>. This class has no operation list or, in other words, this list is empty. It means that there are no operation-specific features (like operator<< if boost::dynamic_any::to_ostream is in the operation list) nor operation-specific compile-time requirements for stored types (aka stored type should be less-comparable if boost::dynamic_any::less is in the list). Thus, this class is minimalistic in terms of delivered features as well as in terms of compile-time requirements for stored type.
There is only one requirement for stored type T: it must be ValueType. Informally, T is ValueType if objects of type T behave like values. Values have identity and they can be copied without loosing they identities. From formal point of view, T must have copy constructor. For example int, char and std::string are ValueTypes. Most interesting example of non ValueType is monostate. Although monostate objects can be copied they don't have identity - they share it among all copies. It's possible to store such types in any but it's not recommended.
First of all, any must be constructed. Let's construct an any with empty content and check that it really holds nothing:
boost::dynamic_any::any<> nothing; assert(nothing.empty()); assert(nothing.type() == typeid(void)); |
An any can be also constructed from an ordinary value or from other any value. Let's put a string into one any and then copy it:
boost::dynamic_any::any<> something(std::string("s")); boost::dynamic_any::any<> copy(something); assert(!copy.empty() && !something.empty()); assert(copy.type() == something.type()); |
You can change an any content later using assignment operator:
something = ' '; // assign new value copy = something; // copy assignment |
Note that different any class templates cannot be copied or assigned to each other (printable_any typedef is taken from Hello, World! example):
printable_any x(std::string("Hello")); boost::dynamic_any::any<> y(x); // error! boost::dynamic_any::any<> z; z = x; // error! |
Fortunately, conversion between different any types exists.
It can be done through grab,
assign or
exchange member-functions.
Extraction
Extraction is implemented as free extract functions and is used to get access to stored value. Stored value can be accessed in three ways: (a) as a copy (by value), (b) through reference or (c) through pointer. First two ways are handled by one extract function (actually, by two functions which differ in constness of the argument).
Extraction by value and by reference demonstrated in the following code fragment:
boost::dynamic_any::any<> i(1); int copy = extract<int>(i); int & ref = extract<int &>(i); const int & const_ref = extract<const int &>(i); |
How to dereference a pointer to unknown type? How to compare two values of unknown types? The answer to these and many other similar questions is standard operations. They defined in <boost/dynamic_any/operations.hpp> header file, don't forget to include it before use.
In the next lines of code super_any is defined. If you look at super_any typedef you'll see that it has support for less, dereference and to_ostream standard operations.
typedef boost::dynamic_any::any< boost::mpl::list< boost::dynamic_any::less, boost::dynamic_any::dereference, boost::dynamic_any::to_ostream > > super_any; |
Our super_any can hold types which meet all operation-specific requirements. The less operation requires that binary operator< with normal LessComparable semantics is provided for stored type. For to_ostream it is required that stored type can be printed to ostream.
Requirements for dereference are different because this operation don't have fixed result. It always returns the same type as it accepts. If dereference is applied to super_any then super_any is returned. As a result, dereference restriction rules depends both on stored type and on the final any class template definition (super_any in this case). Type T * can be stored by super_any if type T meet requirements for all operations of the super_any. For non-pointer type T it must meet requirements of all operations except dereference itself. Probably, these rules seem to be a little bit complicated but when you read how to define your own operation you'll see that these rules have a logical base.
In the next code fragments only std::string and std::string * is used as super_any content. It can be checked that these types meet all requirements for super_any. Let's begin with operator based code and then compare it with function-like style of operation calls.
std::string abc("abc"); std::string zoo("zoo"); super_any p(&abc); // p holds address of abc std::cout < < p << '\n'; // print pointer super_any a = *p; // dereference super_any z = zoo; // compare and print to standard output if(a < z) std::cout << a << " < " << z; if(z < a) std::cout << z << " < " << a; |
First interesting line is the line where p is dereferenced. Although is looks like ordinal dereference there are two principal differences. First is that rvalue is returned and second that dereferenced type is the same as original. Why it's so? Because not type is dereferenced but rather its content is. While p holds pointer to std::string, *p holds a copy of the string to which content of p points. Dereference operation can be applied when the any holds zero pointer or not a pointer. In this case exception is thrown.
Printing and comparison are much simpler and their analysis is skipped.
Function based code is almost the same with replacement of operators with functors. May be for standard operations it's better to use operators but for user-defined operations it's only the case:
// function objects boost::dynamic_any::less less; boost::dynamic_any::dereference dereference; boost::dynamic_any::to_ostream print(std::cout); std::string abc("abc"); std::string zoo("zoo"); super_any p(&abc); // p holds address of abc print(p); // print pointer std::cout << \n'; super_any a = dereference(p); super_any z = zoo; // compare and print to standard output if(less(a, z)) { print(a); std::cout << " < "; print(z); } if(less(z, a)) { print(z); std::cout << " < "; print(a); } |
Some notes on comparison operations is needed. All comparison operations compare first by types and if types are equal then content. The std::type_info::before member-function is used to compare two any values with content of different types.
Arithmetic operations (plus, minus and so on) don't behave this way. It's not possible to add two any values if they contain unrelated types (types are related if they are equal or one is public unambiguous base of another).
This property of comparison operations can help to define heterogeneous associative container like any_set below:
typedef std::set< boost::mpl::list< boost::dynamic_any::less > > any_set; |
Revised 24 March, 2003
© Copyright Alexander Nasonov 2002-2003. All Rights Reserved.