Boost.Convert with Standard Algorithms

| Home | Libraries | People | FAQ | More

## Boost.Convert with Standard Algorithms

The following code demonstrates a failed attempt (and one of the reasons Boost.Convert has been developed) to convert a few `string`s to `int`s with `boost::lexical_cast`:

    boost::array<char const*, 3> strs = {{ " 5", "0XF", "not an int" }};
    std::vector<int>             ints;

    try
    {
        std::transform(strs.begin(), strs.end(), std::back_inserter(ints),
            boost::bind(boost::lexical_cast<int, string>, _1));

        BOOST_TEST(0 && "Never reached!");
    }
    catch (std::exception&)
    {
        BOOST_TEST(ints.size() == 0); // No strings converted.
    }

If the exception-throwing behavior is the desired behavior, then Boost.Convert supports that. In addition, it also provides a non-throwing process-flow:

    boost::array<char const*, 3> strs = {{ " 5", "0XF", "not an int" }};
    std::vector<int>             ints;

    std::transform(strs.begin(), strs.end(), std::back_inserter(ints),
        boost::cnv::apply<int>(boost::cnv::lexical_cast()).value_or(-1));

    BOOST_TEST(ints.size() == 3);
    BOOST_TEST(ints[0] == -1); // Failed conversion does not throw.
    BOOST_TEST(ints[1] == -1); // Failed conversion does not throw.
    BOOST_TEST(ints[2] == -1); // Failed conversion does not throw.

Deploying `boost::cnv::cstream` with better formatting capabilities yields better results with exception-throwing and non-throwing process-flows still supported:

    boost::array<char const*, 3> strs = {{ " 5", "0XF", "not an int" }};
    std::vector<int>             ints;
    boost::cnv::cstream           cnv;

    try
    {
        std::transform(strs.begin(), strs.end(), std::back_inserter(ints),
            boost::cnv::apply<int>(boost::cref(cnv(std::hex)(std::skipws))));

        BOOST_TEST(0 && "Never reached!");
    }
    catch (boost::bad_optional_access const&)
    {
        BOOST_TEST(ints.size() == 2); // Only the first two strings converted.
        BOOST_TEST(ints[0] ==  5);    // " 5"
        BOOST_TEST(ints[1] == 15);    // "0XF"

        // "not an int" causes the exception thrown.
    }

    std::transform(strs.begin(), strs.end(), std::back_inserter(ints),
        boost::cnv::apply<int>(boost::cref(cnv(std::hex)(std::skipws))).value_or(-1));

    BOOST_TEST(ints.size() == 3);
    BOOST_TEST(ints[0] ==  5);
    BOOST_TEST(ints[1] == 15);
    BOOST_TEST(ints[2] == -1); // Failed conversion

| Important
One notable difference in the deployment of `boost::cnv::cstream` with algorithms is the use of `boost::cref` (or `std::cref` in C++11). It needs to be remembered that with standard algorithms the deployed converter needs to be copyable or movable (C++11) and is, in fact, copied or moved by the respective algorithm before being used. Given that `std::cstringstream` is not copyable, `boost::cnv::cstream` is not copyable either. That limitation is routinely worked-around using `boost::ref` or `boost::cref`.

And now an example of algorithm-based integer-to-string formatted conversion with `std::hex`, `std::uppercase` and `std::showbase` formatting applied:

    boost::array<int, 3>     ints = {{ 15, 16, 17 }};
    std::vector<std::string> strs;
    boost::cnv::cstream       cnv;

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

    std::transform(ints.begin(), ints.end(), std::back_inserter(strs),
        boost::cnv::apply<string>(boost::cref(cnv)));

    BOOST_TEST(strs.size() == 3);
    BOOST_TEST(strs[0] ==  "0XF"); // 15
    BOOST_TEST(strs[1] == "0X10"); // 16
    BOOST_TEST(strs[2] == "0X11"); // 17

#### Forced TypeIn vs. Deduced

So far it was sufficient to explicitly specify only one type to `boost::cnv::apply<TypeOut>` -- the target TypeOut type. The source TypeIn type has been provided implicitly through the algorithm and often it all just works (as the examples above demonstrate). However, at times more control is needed regarding the TypeIn type and `boost::cnv::apply()` provides such control via explicit specification of TypeIn -- `boost::cnv::apply<TypeOut, TypeIn>`.

The following example demonstrates an interesting issue related to the `change` class introduced in Integration of User-Defined Types. The class is essentially a glorified `enum`, a user-friendly (and safer in the C++03 world) convenience wrapper around the actual `enum value_type { no, up, dn }`. In particular, an array of `change` values (`chgs1`) sensibly converts to readable "no", "up" and "dn" strings (`strs1`) when an array of `change::value_type` values (`chgs2`) converts to obscure "0", "1" and "2" (`strs2`).

    boost::array<change, 3>             chgs1 = {{ change::no, change::up, change::dn }};
    boost::array<change::value_type, 3> chgs2 = {{ change::no, change::up, change::dn }};
    std::vector<std::string>            strs1;
    std::vector<std::string>            strs2;
    std::vector<std::string>            strs3;
    boost::cnv::cstream                   cnv;

    std::transform(chgs1.begin(), chgs1.end(), std::back_inserter(strs1),
        boost::cnv::apply<string>(boost::cref(cnv))); // Deduced TypeIn is 'change'

    std::transform(chgs2.begin(), chgs2.end(), std::back_inserter(strs2),
        boost::cnv::apply<string>(boost::cref(cnv))); // Deduced TypeIn is 'change::value_type'

    BOOST_TEST(strs1.size() == 3);
    BOOST_TEST(strs1[0] == "no");
    BOOST_TEST(strs1[1] == "up");
    BOOST_TEST(strs1[2] == "dn");

    BOOST_TEST(strs2.size() == 3);
    BOOST_TEST(strs2[0] == "0");
    BOOST_TEST(strs2[1] == "1");
    BOOST_TEST(strs2[2] == "2");

The `boost::cnv::apply<TypeOut, TypeIn>` with forced (rather than deduced) TypeIn comes to the rescue and converts the array of `change::value_type` values (`chgs2`) to sensible "no", "up" and "dn" (`strs3`):

    std::transform(chgs2.begin(), chgs2.end(), std::back_inserter(strs3),
        boost::cnv::apply<string, change>(boost::cref(cnv)));

    BOOST_TEST(strs3.size() == 3);
    BOOST_TEST(strs3[0] == "no");
    BOOST_TEST(strs3[1] == "up");
    BOOST_TEST(strs3[2] == "dn");

| Note
For demonstration purposes the example above is as simple as possible and, consequently, the described "issue" could probably be addressed by other means. Still, do not let my inability to come up with a better (complex but short and succulent) example get in the way of appreciating the described functionality. I do not expect it to be used often but it is here when you need it.