PrevUpHomeNext

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 strings to ints with boost::lexical_cast:

std::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),
        std::bind(boost::lexical_cast<int, string>, std::placeholders::_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 supports a non-throwing process-flow:

std::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:

std::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>(std::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>(std::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] Important

One notable difference in the deployment of boost::cnv::cstream with algorithms is the use of std::cref.

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 std::ref or std::cref.

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

std::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>(std::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 was 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 convenience wrapper around the actual enum value_type { no, up, dn }. In the example an array of change values (chgs1) is sensibly converted 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).

std::array<change, 3>             chgs1 = {{ change::no, change::up, change::dn }};
std::array<change::value_type, 3> chgs2 = {{ change::no, change::up, change::dn }};

auto strs1 = std::vector<std::string>();
auto strs2 = std::vector<std::string>();
auto strs3 = std::vector<std::string>();
auto   cnv = boost::cnv::cstream();

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

std::transform(chgs2.begin(), chgs2.end(), std::back_inserter(strs2),
    boost::cnv::apply<string>(std::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>(std::cref(cnv)));

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

[Note] Note

For demonstration purposes the example above is made 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 representative) 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.


PrevUpHomeNext