...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
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 supports 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
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 |
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
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
).
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 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 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. |