...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
"Detection is, or ought to be, an exact science, ..." Sir Arthur Conan Doyle
int i2 = convert<int>("not an int", cnv).value_or(-1); // after the call i2==-1 if (i2 == -1) process_failure();
The code above is straightforward and self-explanatory but, strictly speaking, is not entirely correct as -1 might be the result of a conversion failure or the successful conversion of the "-1" string. Still, in reality "spare" values (outside the valid/sensible range) are often available to indicate conversion failures. If so, such straightorward deployment might be adequate. Alternatively, it might be not that uncommon to ignore conversion failures altogether and to simply log the event and to proceed with the supplied fallback value.
Applications outside these mentioned categories still require conversion failure
reliably detected and processed accordingly. The boost::lexical_cast
's
(only) answer is to throw on failure and Boost.Convert
supports that behavior as well:
try { int i1 = lexical_cast<int>(str); // Throws if the conversion fails. int i2 = convert<int>(str, cnv).value(); // Throws if the conversion fails. } catch (...) { process_failure(); }
However, to cater for a wider range of program-flow variations, Boost.Convert adds the flexibility of
optional<int> r1 = convert<int>(str1, cnv); // Does not throw on conversion failure. optional<int> r2 = convert<int>(str2, cnv); // Does not throw on conversion failure. // ... try // Delayed processing of potential exceptions. { int i1 = r1.value(); // Will throw if conversion failed. int i2 = r2.value(); // Will throw if conversion failed. } catch (boost::bad_optional_access const&) { // Handle failed conversion. } // Exceptions are avoided altogether. int i1 = r1 ? r1.value() : fallback_value; int i2 = r2.value_or(fallback_value); int i3 = convert<int>(str3, cnv).value_or(fallback_value); int i4 = convert<int>(str3, cnv).value_or_eval(fallback_function);
Here boost::optional
steps forward as the actual type returned by boost::convert()
which until now we avoided by immediately
calling its value-accessor methods:
int i1 = boost::convert<int>(str1, cnv).value(); int i2 = boost::convert<int>(str2, cnv).value_or(fallback_value); int i3 = boost::convert<int>(str3, cnv).value_or_eval(fallback_function);
Note | |
---|---|
One notable advantage of |
From the user perspective, boost::lexical_cast
processes failure in a somewhat one-dimensional non-negotiable manner. boost::convert
takes a more flexible approach. It provides choice and leaves the decision
to the user. It is not unimaginable that, on the library level, propagating
the conversion-failure exception might be the only available option. On the
application level though, in my personal experience, the choice has overwhelmingly
been to handle conversion failures locally, i.e. avoiding
conversion-failure exception propagation or, better still, avoiding exceptions
altogether with program flows similar to:
boost::optional<int> res = boost::convert<int>(str, cnv); if (!res) log("str conversion failed!"); int i1 = res.value_or(fallback_value); // ...proceed
and
struct fallback_func { int operator()() const { log("Failed to convert"); return 42; } };
// Fallback function is called when failed int i2 = convert<int>(str, cnv).value_or_eval(fallback_func()); int i3 = convert<int>(str, cnv, fallback_func()); // Same as above. Alternative API.