...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
using std::string; using boost::lexical_cast; using boost::convert;
boost::cnv::cstream cnv; int i1 = lexical_cast<int>("123"); // Throws when conversion fails. int i2 = convert<int>("123", cnv).value(); // Throws when conversion fails. int i3 = convert<int>("uhm", cnv).value_or(-1); // Returns -1 when conversion fails. BOOST_TEST(i1 == 123); BOOST_TEST(i2 == 123); BOOST_TEST(i3 == -1);
The above is translated to English as
i1
and i2
and
i3
.
The i1
and i2
deployments look sufficiently close
and behave identically. Namely, with the user instructions silent about the
conversion failure, those are treated as "exceptional" and throw.
The i3
specification, on
the other hand, is explicit about conversion failures. The supplied fallback
value is returned if the requested conversion fails.
That basic error detection and processing might be sufficient for a variety of conversion deployments. For example:
int i1 = convert<int>(s1, cnv(std::hex)).value_or(-1); // Read as hex int i2 = convert<int>(s2, cnv(std::dec)).value_or(-1); // Read as decimal if (i1 == -1) log("bad i1"), i1 = default_i1; // Log failure. Proceed with the default if (i2 == -1) log("bad i2"), i2 = default_i2; // Log failure. Proceed with the default // ... proceed
Or
int fallback_fun(char const* msg, int fallback_value) { // The principal advantage of a fallback_func over a fallback_value // is that the former is only called when the conversion request fails. // Consequently, the returned fallback_value is only calculated (which // potentially might be expensive) when it is absolutely necessary. log(msg); return fallback_value; }
int i1 = convert<int>(s1).value_or_eval(std::bind(fallback_fun, "bad i1", default_i1)); int i2 = convert<int>(s2).value_or_eval(std::bind(fallback_fun, "bad i2", default_i2)); // ... proceed
Or, if we do not care about logging conversion failures:
int i1 = convert<int>(s1, cnv(std::hex)).value_or(default_i1); // If failed, proceed with the default int i2 = convert<int>(s2, cnv(std::dec)).value_or(default_i2); // If failed, proceed with the default // ... proceed
So far the deployment of boost::convert
seems more flexible, more compact and natural (your mileage may vary) and
potentially more efficient compared to boost::lexical_cast
which achieves somewhat similar results with:
int i1 = default_i1; try { i1 = lexical_cast<int>(str); } catch (...) { log("bad i1"); }
By design, this is boost::lexical_cast
's
only behavior -- straightforward and comprehensible, but limited. It makes
quite a few legitimate process/program flows difficult and awkward to implement.
Boost.Convert addresses that with additional functionality,
flexibility and convenience.