PrevUpHomeNext

User Interface Signature

The first attempt to accommodate the User Requirements might result in the following fairly conventional interface:

template<typename Out, typename In> Out  convert (In const&); //#1
template<typename Out, typename In> Out  convert (In const&, Out const& fallback); //#2
template<typename Out, typename In> bool convert (Out& result_out, In const&); //#3
template<typename Out, typename In> bool convert (Out& result_out, In const&, Out const& fallback); //#4

with the following behavior:

  1. returns the result or throws on failure (R3a, R4);
  2. does not throw, returns the result or the provided fallback (R3b, R5, R5c but not R5a);
  3. does not throw, writes the result to result_out (when successful), returns indication of success or failure (R3b, R5, R5a but not R5c);
  4. does not throw, writes the result to result_out (when successful) or the provided fallback, returns indication of success or failure (R3b, R5, R5c and R5a).

The #3 and #4 signatures are special as they, in fact, return two things -- the actual result (written into the result_out) and the indication of success or failure (returned by the functions). Given that a reference to result_out is passed in, the actual result_out instance is constructed (storage allocated and initialized) outside the function calls.

Similar to the scenario described in the Converter Signature section that results in an additional and unnecessary overhead. Indeed, if the conversion operation succeeds, then the initialization value is overridden (with the actual result), if it fails, then the value is either overridden still (with the fallback) or is meaningless.

To avoid the overhead we might again (as in the Converter Signature section) deploy boost::optional and to change the signatures to

bool convert (boost::optional<Out>&, In const&); //#3
bool convert (boost::optional<Out>&, In const&, Out const&); //#4

Now, when we look at #3, we can see that the indication of success or failure is duplicated. Namely, it is returned from the function and is encapsulated in boost::optional<Out>. Consequently, #3 can be further simplified to

void convert (boost::optional<Out>&, In const&); //#3

or expressed more idiomatically (in C++) as:

boost::optional<Out> convert (In const&); //#3

So far, we have arrived to the following set

Out                  convert (In const&); //#1
Out                  convert (In const&, Out const&); //#2
boost::optional<Out> convert (In const&); //#3
bool                 convert (boost::optional<Out>&, In const&, Out const&); //#4

which as a whole looks quite ugly and, in fact, does not even compile as #1 clashes with #3. The good thing though is that functionally #1 and #2 are not needed anymore as they are duplicates of the following #3 deployments:

Out out1 = boost::convert(in).value(); // #3 with #1 behavior
Out out2 = boost::convert(in).value_or(fallback); // #3 with #2 behavior

Again, we are not discussing aesthetic aspects of the interface (or syntactic sugar some might say, which might be very subjective). Instead, we are focusing on the functional completeness and so far we manage to maintain the same functional completeness with less.

Turns out, with a bit of effort, we can get away without the most complex one -- #4 -- as well:

boost::optional<Out> out = boost::convert(in);
bool         out_success = out ? true : false;
Out            out_value = out.value_or(fallback);

So, ultimately we arrive to one and only

boost::optional<Out> convert(In const&);

The important qualities of the API are that it is functionally-complete and the most efficient way to deploy the chosen converter signature (see the Converter Signature section). Namely, the boost::convert() interface is routinely optimized out (elided) when deployed as

boost::optional<Out> out = boost::convert(in);

The API has several deployment-related advantages. First, it says exactly what it does. Given a conversion request is only a request, the API returns boost::optional essentially saying "I'll try but I might fail. Proceed as you find appropriate.". Honest and simple. I prefer it to "I'll try. I might fail but you do not want to know about it." or "I'll try. If I fail, you die." or variations along these lines. :-)

On a more serious note though the interface allows for batched conveyor-style conversions. Namely, attempting to convert several values, in sequence, storing the boost::optional results and, then, analyzing/validating them (without losing the information if each individual conversion was successful or not) in some semi-automated way.

Again, that API does not have to be the only API Boost.Convert provides. However, that API is the only essential API. Other APIs are relatively easily derived from it. For example,

template<typename Out, typename In>
Out
convert(In const& in, Out const& fallback) //#2
{
   return convert(in).value_or(fallback);
}

Given that it is extremely difficult (if not impossible) to come up with a library API that could please everyone, we might as well settle on the essential API and let the users build their own APIs (as in the example above) to satisfy their aesthetic preferences.

Still, it needs to be acknowledged that boost::optional is a fairly new concept and some people are reluctant using it or find its deployment unreasonably complicating. Consequently, Boost.Convert provides an alternative (more conventional) interface:

Out convert(In const&, Converter const&, Out const& fallback_value);
Out convert(In const&, Converter const&, Functor const& fallback_functor);
Out convert(In const&, Converter const&, boost::throw_on_failure);

PrevUpHomeNext