boost::cnv::stream Converter

| Home | Libraries | People | FAQ | More

### boost::cnv::stream Converter

The purpose of the converter is to provide conversion-related formatting and locale support not available with `boost::lexical_cast`. Advantages of deploying a `std::stream`-based conversion engine are:

- availability and maturity;
- formatting and locale support;
- familiar interface and deployment;
- instant re-use of available standard manipulators (`std::hex`, `std::setprecision`, `std::skipws`, etc.);
- extendibility via custom manipulators (see Stream Storage for Private Use: iword, pword, and xalloc by Rogue Wave Software).

The converter might be deployed as follows:

    #include <boost/convert.hpp>
    #include <boost/convert/stream.hpp>
    #include <boost/detail/lightweight_test.hpp>

    using std::string;
    using boost::convert;

    struct boost::cnv::by_default : public boost::cnv::cstream {};

    int    i2 = convert<int>("123").value();      // Throws when fails.
    int    i3 = convert<int>("uhm").value_or(-1); // Returns -1 when fails.
    string s2 = convert<string>(123).value();

    BOOST_TEST(i2 == 123);
    BOOST_TEST(i3 == -1);
    BOOST_TEST(s2 == "123");

#### Formatting Support

Formatting support is provided by the underlying `std::stringstream`. Consequently, the API heavily borrows formatting metaphors from this underlying component. One such metaphor is the manipulator represented by `std::hex`, `std::dec`, `std::uppercase`, `std::scientific`, etc.

The following code demonstrates how `char` and `wchar_t` strings can be read in the `std::hex` or `std::dec` format:

    boost::cnv::cstream ccnv;
    boost::cnv::wstream wcnv;

    int v01 = convert<int>("  FF", ccnv(std::hex)(std::skipws)).value_or(0);
    int v02 = convert<int>(L"  F", wcnv(std::hex)(std::skipws)).value_or(0);
    int v03 = convert<int>("  FF", ccnv(std::dec)(std::skipws)).value_or(-5);
    int v04 = convert<int>(L"  F", wcnv(std::dec)(std::skipws)).value_or(-5);

    BOOST_TEST(v01 == 255); // "FF"
    BOOST_TEST(v02 ==  15); // L"F"
    BOOST_TEST(v03 ==  -5); // Failed to convert "FF" as decimal.
    BOOST_TEST(v04 ==  -5); // Failed to convert L"F" as decimal.

For batch-processing it might be more efficient to configure the converter once:

    ccnv(std::showbase)(std::uppercase)(std::hex);

    BOOST_TEST(convert<string>(255, ccnv, "bad") == "0XFF");
    BOOST_TEST(convert<string>( 15, ccnv, "bad") ==  "0XF");

An alternative (generic) formatting interface is currently being extended and explored:

    namespace cnv = boost::cnv;
    namespace arg = boost::cnv::parameter;

    ccnv(arg::base = cnv::base::dec)
        (arg::uppercase = false)
        (arg::notation = cnv::notation::scientific);

is equivalent to the following std::manipulator-based variant:

    ccnv(std::dec)(std::uppercase)(std::scientific);

##### Numeric Base

    using std::string;
    using std::wstring;
    using boost::convert;

The following example demonstrates the deployment of `std::dec`, `std::oct` `std::hex` manipulators:

    boost::cnv::cstream ccnv;

    BOOST_TEST(convert<int>( "11", ccnv(std::hex)).value_or(0) == 17); // 11(16) = 17(10)
    BOOST_TEST(convert<int>( "11", ccnv(std::oct)).value_or(0) ==  9); // 11(8)  = 9(10)
    BOOST_TEST(convert<int>( "11", ccnv(std::dec)).value_or(0) == 11);

    BOOST_TEST(convert<string>( 18, ccnv(std::hex)).value_or("bad") == "12"); // 18(10) = 12(16)
    BOOST_TEST(convert<string>( 10, ccnv(std::oct)).value_or("bad") == "12"); // 10(10) = 12(8)
    BOOST_TEST(convert<string>( 12, ccnv(std::dec)).value_or("bad") == "12");
    BOOST_TEST(convert<string>(255, ccnv(arg::base = boost::cnv::base::oct)).value_or("bad") == "377");
    BOOST_TEST(convert<string>(255, ccnv(arg::base = boost::cnv::base::hex)).value_or("bad") ==  "ff");
    BOOST_TEST(convert<string>(255, ccnv(arg::base = boost::cnv::base::dec)).value_or("bad") == "255");

    ccnv(std::showbase);

    BOOST_TEST(convert<string>(18, ccnv(std::hex)).value_or("bad") == "0x12");
    BOOST_TEST(convert<string>(10, ccnv(std::oct)).value_or("bad") ==  "012");

    ccnv(std::uppercase);

    BOOST_TEST(convert<string>(18, ccnv(std::hex)).value_or("bad") == "0X12");

A more generic interface is also supported:

    namespace cnv = boost::cnv;
    namespace arg = boost::cnv::parameter;

    BOOST_TEST(convert<int>("11", ccnv(arg::base = cnv::base::hex)).value_or(0) == 17);
    BOOST_TEST(convert<int>("11", ccnv(arg::base = cnv::base::oct)).value_or(0) ==  9);
    BOOST_TEST(convert<int>("11", ccnv(arg::base = cnv::base::dec)).value_or(0) == 11);

##### Field Width, Fill Character and Adjustment

    boost::cnv::cstream cnv;

    boost::optional<string> s01 = convert<string>(12, cnv(std::setw(4)));
    boost::optional<string> s02 = convert<string>(12, cnv(std::setw(5))(std::setfill('*')));
    boost::optional<string> s03 = convert<string>(12, cnv(std::setw(5))(std::setfill('*'))(std::left));

    BOOST_TEST(s01 && s01.value() == "  12");  // Field width = 4.
    BOOST_TEST(s02 && s02.value() == "***12"); // Field width = 5, filler = '*'.
    BOOST_TEST(s03 && s03.value() == "12***"); // Field width = 5, filler = '*', left adjustment

It needs to be remembered that `boost::cnv::stream` converter uses `std::stream` as its underlying conversion engine. Consequently, formatting-related behavior are driven by the `std::stream`. Namely, after every operation is performed, the default field width is restored. The values of the fill character and the adjustment remain unchanged until they are modified explicitly.

    // The fill and adjustment remain '*' and 'left'.
    boost::optional<string> s11 = convert<string>(12, cnv(arg::width = 4));
    boost::optional<string> s12 = convert<string>(12, cnv(arg::width = 5)
                                                         (arg::fill = ' ')
                                                         (arg::adjust = cnv::adjust::right));

    BOOST_TEST(s11 && s11.value() == "12**");  // Field width was set to 4.
    BOOST_TEST(s12 && s12.value() == "   12"); // Field width was set to 5 with the ' ' filler.

##### Leading Whitespace Characters

    using std::string;
    using std::wstring;
    using boost::convert;

        boost::cnv::cstream    ccnv;
        char const* const cstr_good = "  123";
        char const* const  cstr_bad = "  123 "; // std::skipws only affects leading spaces.

        ccnv(std::skipws);        // Ignore leading whitespaces
    //  ccnv(arg::skipws = true); // Ignore leading whitespaces. Alternative interface

        BOOST_TEST(convert<int>(cstr_good, ccnv).value_or(0) == 123);
        BOOST_TEST(convert<string>("  123", ccnv).value_or("bad") == "123");

        BOOST_TEST(!convert<int>(cstr_bad, ccnv));

        ccnv(std::noskipws);       // Do not ignore leading whitespaces
    //  ccnv(arg::skipws = false); // Do not ignore leading whitespaces. Alternative interface

        // All conversions fail.
        BOOST_TEST(!convert<int>(cstr_good, ccnv));
        BOOST_TEST(!convert<int>( cstr_bad, ccnv));

##### Format of Boolean Values

    using std::string;
    using std::wstring;
    using boost::convert;

    BOOST_TEST(convert<string>( true, cnv(std::boolalpha)).value_or("bad") ==  "true");
    BOOST_TEST(convert<string>(false, cnv(std::boolalpha)).value_or("bad") == "false");

    BOOST_TEST(convert<bool>( "true", cnv(std::boolalpha)).value_or(false) ==  true);
    BOOST_TEST(convert<bool>("false", cnv(std::boolalpha)).value_or( true) == false);

    BOOST_TEST(convert<string>( true, cnv(std::noboolalpha)).value_or("bad") == "1");
    BOOST_TEST(convert<string>(false, cnv(std::noboolalpha)).value_or("bad") == "0");

    BOOST_TEST(convert<bool>("1", cnv(std::noboolalpha)).value_or(false) ==  true);
    BOOST_TEST(convert<bool>("0", cnv(std::noboolalpha)).value_or( true) == false);