If you want to take advantage of the real power of Boost and C++ and avoid the confusion about which library to use in which situation, then this book is for you.
Beginning with the basics of Boost C++, you will move on to learn how the Boost libraries simplify application development. You will learn to convert data such as string to numbers, numbers to string, numbers to numbers and more. Managing resources will become a piece of cake. You’ll see what kind of work can be done at compile time and what Boost containers can do. You will learn everything for the development of high quality fast and portable applications. Write a program once and then you can use it on Linux, Windows, MacOS, Android operating systems. From manipulating images to graphs, directories, timers, files, networking – everyone will find an interesting topic.
Be sure that knowledge from this book won’t get outdated, as more and more Boost libraries become part of the C++ Standard.
Using a safer way to work with a container that stores multiple chosen types
Imagine that you are creating a wrapper
around
some SQL database interface. You decided that
boost::any
will perfectly match the requirements for a single cell of the database table.
Some other programmer will use your classes, and his/her task would be to get a row from the database and count the sum of the arithmetic types in a row.
This is what such a code would look like:
#include <boost/any.hpp>
#include <vector>
#include <string>
#include <typeinfo>
#include <algorithm>
#include <iostream>
// This typedefs and methods will be in our header,
// that wraps around native SQL interface.
typedef boost::any cell_t;
typedef std::vector<cell_t> db_row_t;
// This is just an example, no actual work with database.
db_row_t get_row(const char* /*query*/) {
// In real application 'query' parameter shall have a 'const
// char*' or 'const std::string&' type? See recipe "Type
// 'reference to string'" for an answer.
db_row_t row;
row.push_back(10);
row.push_back(10.1f);
row.push_back(std::string("hello again"));
return row;
// This is how a user will use your classes
struct db_sum {
private:
double& sum_;
public:
explicit db_sum(double& sum)
: sum_(sum)
void operator()(const cell_t& value) {
const std::type_info& ti = value.type();
if (ti == typeid(int)) {
sum_ += boost::any_cast<int>(value);
} else if (ti == typeid(float)) {
sum_ += boost::any_cast<float>(value);
int main() {
db_row_t row = get_row("Query: Give me some row, please.");
double res = 0.0;
std::for_each(row.begin(), row.end(), db_sum(res));
std::cout << "Sum of arithmetic types in database row is: "
<< res << std::endl;
}
If you compile and run this example, it will output a correct answer:
Sum of arithmetic types in database row is: 20.1
Do you remember what your own thoughts were when reading the implementation of
operator()
?
I guess they were,
"And what about double, long, short, unsigned, and other types?"
The same thoughts will come into the head of a programmer who will use your interface.
So, you need to carefully document values stored by your
cell_t
or use a more elegant solution as described in the following sections.
Reading the previous two recipes is highly recommended if you are not already familiar with the
Boost.Variant
and
Boost.Any
libraries.
The
Boost.Variant
library implements a visitor programming pattern for accessing the stored data, which is much safer than getting values via
boost::get<>
. This pattern forces the programmer to take care of each type in variant, otherwise the code will fail to compile. You can use this pattern via the
boost::apply_visitor
function, which takes a
visitor
functional object as the first parameter and a
variant
as the second parameter. If you are using a pre C++14 compiler, then
visitor
functional objects must derive from the
boost::static_visitor<T>
class, where
T
is a type being returned by a
visitor
. A
visitor
object must have overloads of
operator()
for each type stored by a variant.
Let's change the
cell_t
type to
boost::variant<int, float, string>
and modify our example:
#include <boost/variant.hpp>
#include <vector>
#include <string>
#include <iostream>
// This typedefs and methods will be in header,
// that wraps around native SQL interface.
typedef boost::variant<int, float, std::string> cell_t;
typedef std::vector<cell_t> db_row_t;
// This is just an example, no actual work with database.
db_row_t get_row(const char* /*query*/) {
// See recipe "Type 'reference to string'"
// for a better type for 'query' parameter.
db_row_t row;
row.push_back(10);
row.push_back(10.1f);
row.push_back("hello again");
return row;
// This is a code required to sum values.
// We can provide no template parameter
// to boost::static_visitor<> if our visitor returns nothing.
struct db_sum_visitor: public boost::static_visitor<double> {
double operator()(int value) const {
return value;
double operator()(float value) const {
return value;
double operator()(const std::string& /*value*/) const {
return 0.0;
int main() {
db_row_t row = get_row("Query: Give me some row, please.");
double res = 0.0;
for (auto it = row.begin(), end = row.end(); it != end; ++it) {
res += boost::apply_visitor(db_sum_visitor(), *it);
std::cout << "Sum of arithmetic types in database row is: "
<< res << std::endl;
}
At compile time, the
Boost.Variant
library generates a big
switch
statement, each case of which calls a
visitor
for a single type from the variant's list of types. At runtime, the index of the stored type is retrieved using
which()
and jumps
to a correct case in
switch
statement is made. Something like this will be generated for
boost::variant<int, float, std::string>
:
switch (which())
case 0 /*int*/:
return visitor(*reinterpret_cast<int*>(address()));
case 1 /*float*/:
return visitor(*reinterpret_cast<float*>(address()));
case 2 /*std::string*/:
return visitor(*reinterpret_cast<std::string*>(address()));
default: assert(false);
}
Here, the
address()
function returns a pointer to the internal storage of
boost::variant<int, float, std::string>
.
If we compare this example with the first example in this recipe, we'll see the following advantages of
boost::variant
:
-
We know what types a variable can store
-
If a library writer of the SQL interface adds or modifies a type
held
by a
variant
, we'll
get
a compile-time error instead of incorrect behavior
std::variant
from C++17 also supports visitation. Just write
std::visit
instead of
boost::apply_visitor
and you're done.
Note
You can download
the example code files for all Packt books that you have purchased from your account at
http://www.PacktPub.com
. If you purchased this book elsewhere, you can visit
http://www.PacktPub.com/
support,
and register to have the files emailed directly to you.
-
After reading some recipes from
Chapter 4
,
Compile-Time Tricks
, you'll be able to make generic
visitor
objects that work correctly even if underlying types change
-
Boost's official documentation contains more examples and a description of some other features of
Boost.Variant
; it is available at the following link:
http://boost.org/libs/variant