Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

PrevUpHomeNext

Examples

A polymorphic range formatter
A type-safe printf
Boost.Function with multiple signatures

(For the source of this example see print_sequence.cpp)

This example defines a class hierarchy that allows a sequence to be formatted in several different ways. We'd like to be able to handle any sequence and any stream type, since the range formatting is independent of the formatting of individual elements. Thus, our interface needs to look something like this:

class abstract_printer {
public:
    template<class CharT, class Traits, class Range>
    virtual void print(std::basic_ostream<CharT, Traits>& os, const Range& r) const = 0;
};

Unfortunately, this is illegal because a virtual function cannot be a template. However, we can define a class with much the same behavior using Boost.TypeErasure.

#include <boost/type_erasure/any.hpp>
#include <boost/type_erasure/iterator.hpp>
#include <boost/type_erasure/operators.hpp>
#include <boost/type_erasure/tuple.hpp>
#include <boost/type_erasure/same_type.hpp>
#include <boost/range/begin.hpp>
#include <boost/range/end.hpp>
#include <boost/range/iterator.hpp>
#include <iostream>
#include <iomanip>
#include <vector>

using namespace boost::type_erasure;

struct _t : placeholder {};
struct _iter : placeholder {};
struct _os : placeholder {};

template<class T, class U = _self>
struct base_and_derived
{
    static T& apply(U& arg) { return arg; }
};

namespace boost {
namespace type_erasure {

template<class T, class U, class Base>
struct concept_interface<base_and_derived<T, U>, Base, U> : Base
{
    operator typename rebind_any<Base, const T&>::type() const
    {
        return call(base_and_derived<T, U>(), const_cast<concept_interface&>(*this));
    }
    operator typename rebind_any<Base, T&>::type()
    {
        return call(base_and_derived<T, U>(), *this);
    }
};

}
}

// abstract_printer - An abstract base class for formatting sequences.
class abstract_printer {
public:
    // print - write a sequence to a std::ostream in a manner
    //   specific to the derived class.
    //
    // Requires: Range must be a Forward Range whose elements can be
    //   printed to os.
    template<class CharT, class Traits, class Range>
    void print(std::basic_ostream<CharT, Traits>& os, const Range& r) const {
        // Capture the arguments
        typename boost::range_iterator<const Range>::type
            first(boost::begin(r)),
            last(boost::end(r));
        tuple<requirements, _os&, _iter, _iter> args(os, first, last);
        // and forward to the real implementation
        do_print(get<0>(args), get<1>(args), get<2>(args));
    }
    virtual ~abstract_printer() {}
protected:
    // define the concept requirements of the arguments of
    // print and typedef the any types.
    typedef boost::mpl::vector<
        base_and_derived<std::ios_base, _os>,
        ostreamable<_os, _t>,
        ostreamable<_os, const char*>,
        forward_iterator<_iter, const _t&>,
        same_type<_t, forward_iterator<_iter, const _t&>::value_type>
    > requirements;
    typedef boost::type_erasure::any<requirements, _os&> ostream_type;
    typedef boost::type_erasure::any<requirements, _iter> iterator_type;
    // do_print - This method must be implemented by derived classes
    virtual void do_print(
        ostream_type os, iterator_type first, iterator_type last) const = 0;
};

// separator_printer - writes the elements of a sequence
//   separated by a fixed string.  For example, if
//   the separator is ", " separator_printer produces
//   a comma separated list.
class separator_printer : public abstract_printer {
public:
    explicit separator_printer(const std::string& sep) : separator(sep) {}
protected:
    virtual void do_print(
        ostream_type os, iterator_type first, iterator_type last) const {
        if(first != last) {
            os << *first;
            ++first;
            for(; first != last; ++first) {
                os << separator.c_str() << *first;
            }
        }
    }
private:
    std::string separator;
};

// column_separator_printer - like separator_printer, but
//   also inserts a line break after every n elements.
class column_separator_printer : public abstract_printer {
public:
    column_separator_printer(const std::string& sep, std::size_t num_columns)
      : separator(sep),
        cols(num_columns)
    {}
protected:
    virtual void do_print(
        ostream_type os, iterator_type first, iterator_type last) const {
        std::size_t count = 0;
        for(; first != last; ++first) {
            os << *first;
            boost::type_erasure::any<requirements, _iter> temp = first;
            ++temp;
            if(temp != last) {
                os << separator.c_str();
            }
            if(++count % cols == 0) {
                os << "\n";
            }
        }
    }
private:
    std::string separator;
    std::size_t cols;
};

// aligned_column_printer - formats a sequence in columns
//   reading down.  For example, given the sequence
//   { 1, 2, 3, 4, 5 }, aligned_column_printer might print
//   1   4
//   2   5
//   3
class aligned_column_printer : public abstract_printer {
public:
    aligned_column_printer(std::size_t column_width, std::size_t num_columns)
      : width(column_width),
        cols(num_columns)
    {}
protected:
    virtual void do_print(
        ostream_type os, iterator_type first, iterator_type last) const
    {
        if(first == last) return;
        std::vector<iterator_type> column_iterators;

        // find the tops of the columns
        std::size_t count = 0;
        for(iterator_type iter = first; iter != last; ++iter) {
            ++count;
        }
        std::size_t rows = (count + cols - 1) / cols;
        count = 0;
        for(iterator_type iter = first; iter != last; ++iter) {
            if(count % rows == 0) {
                column_iterators.push_back(iter);
            }
            ++count;
        }

        iterator_type last_col = column_iterators.back();

        // print the full rows
        while(column_iterators.back() != last) {
            for(std::vector<iterator_type>::iterator
                iter = column_iterators.begin(),
                end = column_iterators.end(); iter != end; ++iter)
            {
                static_cast<std::ios_base&>(os).width(width);
                os << **iter;
                ++*iter;
            }
            os << "\n";
        }

        // print the rows that are missing the last column
        column_iterators.pop_back();
        if(!column_iterators.empty()) {
            while(column_iterators.back() != last_col) {
                for(std::vector<iterator_type>::iterator
                    iter = column_iterators.begin(),
                    end = column_iterators.end(); iter != end; ++iter)
                {
                    static_cast<std::ios_base&>(os).width(width);
                    os << **iter;
                    ++*iter;
                }
                os << "\n";
            }
        }
    }
private:
    std::size_t width;
    std::size_t cols;
};

int main() {
    int test[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    separator_printer p1(",");
    p1.print(std::cout, test);
    std::cout << std::endl;
    column_separator_printer p2(",", 4);
    p2.print(std::cout, test);
    std::cout << std::endl;
    aligned_column_printer p3(16, 4);
    p3.print(std::cout, test);
}

(For the source of this example see printf.cpp)

This example uses the library to implement a type safe printf.

[Note] Note

This example uses C++11 features. You'll need a recent compiler for it to work.

#include <boost/type_erasure/builtin.hpp>
#include <boost/type_erasure/operators.hpp>
#include <boost/type_erasure/any_cast.hpp>
#include <boost/type_erasure/any.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/io/ios_state.hpp>
#include <iostream>
#include <sstream>
#include <iomanip>
#include <vector>
#include <string>

namespace mpl = boost::mpl;
using namespace boost::type_erasure;
using namespace boost::io;

// We capture the arguments by reference and require nothing
// except that each one must provide a stream insertion operator.
typedef any<
    mpl::vector<
        typeid_<>,
        ostreamable<>
    >,
    const _self&
> any_printable;
typedef std::vector<any_printable> print_storage;

// Forward declaration of the implementation function
void print_impl(std::ostream& os, const char * format, const print_storage& args);

// print
//
// Writes values to a stream like the classic C printf function.  The
// arguments are formatted based on specifiers in the format string,
// which match the pattern:
//
// '%' [ argument-number '$' ] flags * [ width ] [ '.' precision ] [ type-code ] format-specifier
//
// Other characters in the format string are written to the stream unchanged.
// In addition the sequence, "%%" can be used to print a literal '%' character.
// Each component is explained in detail below
//
// argument-number:
//   The value must be between 1 and sizeof... T.  It indicates the
//   index of the argument to be formatted.  If no index is specified
//   the arguments will be processed sequentially.  If an index is
//   specified for one argument, then it must be specified for every argument.
//
// flags:
//   Consists of zero or more of the following:
//   '-': Left justify the argument
//   '+': Print a plus sign for positive integers
//   '0': Use leading 0's to pad instead of filling with spaces.
//   ' ': If the value doesn't begin with a sign, prepend a space
//   '#': Print 0x or 0 for hexadecimal and octal numbers.
//
// width:
//   Indicates the minimum width to print.  This can be either
//   an integer or a '*'.  an asterisk means to read the next
//   argument (which must have type int) as the width.
//
// precision:
//   For numeric arguments, indicates the number of digits to print.  For
//   strings (%s) the precision indicates the maximum number of characters
//   to print.  Longer strings will be truncated.  As with width
//   this can be either an integer or a '*'.  an asterisk means
//   to read the next argument (which must have type int) as
//   the width.  If both the width and the precision are specified
//   as '*', the width is read first.
//
// type-code:
//   This is ignored, but provided for compatibility with C printf.
//
// format-specifier:
//   Must be one of the following characters:
//   d, i, u: The argument is formatted as a decimal integer
//   o:       The argument is formatted as an octal integer
//   x, X:    The argument is formatted as a hexadecimal integer
//   p:       The argument is formatted as a pointer
//   f:       The argument is formatted as a fixed point decimal
//   e, E:    The argument is formatted in exponential notation
//   g, G:    The argument is formatted as either fixed point or using
//            scientific notation depending on its magnitude
//   c:       The argument is formatted as a character
//   s:       The argument is formatted as a string
//
template<class... T>
void print(std::ostream& os, const char * format, const T&... t)
{
    // capture the arguments
    print_storage args = { any_printable(t)... };
    // and forward to the real implementation
    print_impl(os, format, args);
}

// This overload of print with no explicit stream writes to std::cout.
template<class... T>
void print(const char * format, const T&... t)
{
    print(std::cout, format, t...);
}

// The implementation from here on can be separately compiled.

// utility function to parse an integer
int parse_int(const char *& format) {
    int result = 0;
    while(char ch = *format) {
        switch(ch) {
        case '0': case '1': case '2': case '3': case '4':
        case '5': case '6': case '7': case '8': case '9':
            result = result * 10 + (ch - '0');
            break;
        default: return result;
        }
        ++format;
    }
    return result;
}

// printf implementation
void print_impl(std::ostream& os, const char * format, const print_storage& args) {
    int idx = 0;
    ios_flags_saver savef_outer(os, std::ios_base::dec);
    bool has_positional = false;
    bool has_indexed = false;
    while(char ch = *format++) {
        if (ch == '%') {
            if (*format == '%') { os << '%'; continue; }

            ios_flags_saver savef(os);
            ios_precision_saver savep(os);
            ios_fill_saver savefill(os);

            int precision = 0;
            bool pad_space = false;
            bool pad_zero = false;

            // parse argument index
            if (*format != '0') {
                int i = parse_int(format);
                if (i != 0) {
                    if(*format == '$') {
                        idx = i - 1;
                        has_indexed = true;
                        ++format;
                    } else {
                        os << std::setw(i);
                        has_positional = true;
                        goto parse_precision;
                    }
                } else {
                    has_positional = true;
                }
            } else {
                has_positional = true;
            }

            // Parse format modifiers
            while((ch = *format)) {
                switch(ch) {
                case '-': os << std::left; break;
                case '+': os << std::showpos; break;
                case '0': pad_zero = true; break;
                case ' ': pad_space = true; break;
                case '#': os << std::showpoint << std::showbase; break;
                default: goto parse_width;
                }
                ++format;
            }

        parse_width:
            int width;
            if (*format == '*') {
                ++format;
                width = any_cast<int>(args.at(idx++));
            } else {
                width = parse_int(format);
            }
            os << std::setw(width);

        parse_precision:
            if (*format == '.') {
                ++format;
                if (*format == '*') {
                    ++format;
                    precision = any_cast<int>(args.at(idx++));
                } else {
                    precision = parse_int(format);
                }
                os << std::setprecision(precision);
            }

            // parse (and ignore) the type modifier
            switch(*format) {
            case 'h': ++format; if(*format == 'h') ++format; break;
            case 'l': ++format; if(*format == 'l') ++format; break;
            case 'j':
            case 'L':
            case 'q':
            case 't':
            case 'z':
                ++format; break;
            }

            std::size_t truncate = 0;

            // parse the format code
            switch(*format++) {
            case 'd': case 'i': case 'u': os << std::dec; break;
            case 'o': os << std::oct; break;
            case 'p': case 'x': os << std::hex; break;
            case 'X': os << std::uppercase << std::hex; break;
            case 'f': os << std::fixed; break;
            case 'e': os << std::scientific; break;
            case 'E': os << std::uppercase << std::scientific; break;
            case 'g': break;
            case 'G': os << std::uppercase; break;
            case 'c': case 'C': break;
            case 's': case 'S': truncate = precision; os << std::setprecision(6); break;
            default: assert(!"Bad format string");
            }

            if (pad_zero && !(os.flags() & std::ios_base::left)) {
                os << std::setfill('0') << std::internal;
                pad_space = false;
            }

            if (truncate != 0 || pad_space) {
                // These can't be handled by std::setw.  Write to a stringstream and
                // pad/truncate manually.
                std::ostringstream oss;
                oss.copyfmt(os);
                oss << args.at(idx++);
                std::string data = oss.str();

                if (pad_space) {
                    if (data.empty() || (data[0] != '+' && data[0] != '-' && data[0] != ' ')) {
                        os << ' ';
                    }
                }
                if (truncate != 0 && data.size() > truncate) {
                    data.resize(truncate);
                }
                os << data;
            } else {
                os << args.at(idx++);
            }

            // we can't have both positional and indexed arguments in
            // the format string.
            assert(has_positional ^ has_indexed);

        } else {
            std::cout << ch;
        }
    }
}

int main() {
    print("int: %d\n", 10);
    print("int: %0#8X\n", 0xA56E);
    print("double: %g\n", 3.14159265358979323846);
    print("double: %f\n", 3.14159265358979323846);
    print("double: %+20.9e\n", 3.14159265358979323846);
    print("double: %0+20.9g\n", 3.14159265358979323846);
    print("double: %*.*g\n", 20, 5, 3.14159265358979323846);
    print("string: %.10s\n", "Hello World!");
    print("double: %2$*.*g int: %1$d\n", 10, 20, 5, 3.14159265358979323846);
}

(For the source of this example see multifunction.cpp)

This example implements an extension of Boost.Function that supports multiple signatures.

[Note] Note

This example uses C++11 features. You'll need a recent compiler for it to work.

#include <boost/type_erasure/any.hpp>
#include <boost/type_erasure/builtin.hpp>
#include <boost/type_erasure/callable.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/variant.hpp>
#include <boost/phoenix/core.hpp>
#include <boost/phoenix/operator.hpp>
#include <boost/range/algorithm.hpp>
#include <algorithm>
#include <vector>
#include <string>
#include <iostream>

namespace mpl = boost::mpl;
using namespace boost::type_erasure;
namespace phoenix = boost::phoenix;

// First of all we'll declare the multifunction template.
// multifunction is like Boost.Function but instead of
// taking one signature, it takes any number of them.
template<class... Sig>
using multifunction =
    any<
        mpl::vector<
            copy_constructible<>,
            typeid_<>,
            relaxed,
            callable<Sig>...
        >
    >;

// Let's use multifunction to process a variant.  We'll start
// by defining a simple recursive variant to use.
typedef boost::make_recursive_variant<
    int,
    double,
    std::string,
    std::vector<boost::recursive_variant_> >::type variant_type;
typedef std::vector<variant_type> vector_type;

// Now we'll define a multifunction that can operate
// on the leaf nodes of the variant.
typedef multifunction<void(int), void(double), void(std::string)> function_type;

class variant_handler
{
public:
    void handle(const variant_type& arg)
    {
        boost::apply_visitor(impl, arg);
    }
    void set_handler(function_type f)
    {
        impl.f = f;
    }
private:
    // A class that works with boost::apply_visitor
    struct dispatcher : boost::static_visitor<void>
    {
        // used for the leaves
        template<class T>
        void operator()(const T& t) { f(t); }
        // For a vector, we recursively operate on the elements
        void operator()(const vector_type& v)
        {
            boost::for_each(v, boost::apply_visitor(*this));
        }
        function_type f;
    };
    dispatcher impl;
};

int main() {
    variant_handler x;
    x.set_handler(std::cout << phoenix::val("Value: ") << phoenix::placeholders::_1 << std::endl);

    x.handle(1);
    x.handle(2.718);
    x.handle("The quick brown fox jumps over the lazy dog.");
    x.handle(vector_type{ 1.618, "Gallia est omnis divisa in partes tres", 42 });
}


PrevUpHomeNext