Book Image

Boost C++ Application Development Cookbook - Second Edition

By : Anton Polukhin Alekseevic
Book Image

Boost C++ Application Development Cookbook - Second Edition

By: Anton Polukhin Alekseevic

Overview of this book

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.
Table of Contents (19 chapters)
Title Page
Credits
About the Author
About the Reviewer
www.PacktPub.com
Customer Feedback
Preface

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.

Getting ready

Reading the previous two recipes is highly recommended if you are not already familiar with the Boost.Variant and Boost.Any libraries.

How to do it...

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;
}

How it works...

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 inswitch 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>.

There's more...

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.

See also

  • 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