Basic Conversion-Failure Detection

| Home | Libraries | People | FAQ | More

### Basic Conversion-Failure Detection

    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 calls are expressed in English as

- "convert a string to int" for `i1` and `i2` and
- "convert a string to int and return -1 if the conversion fails" for `i3`.

The `i1` and `i2` deployments look sufficiently close and behave identically. Namely, if the requested conversion fails, an exception is thrown. The user instructions do not describe what to do if conversion fails. Consequently, `boost::lexical_cast` and `boost::convert` treat conversion-failure as "exceptional" and throw.

The `i3` specification, on the other hand, is explicit about conversion failures, so a 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(boost::bind(fallback_fun, "bad i1", default_i1));
    int i2 = convert<int>(s2).value_or_eval(boost::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.