...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
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:
result_out
(when successful), returns indication of success or failure (R3b,
R5, R5a but not R5c);
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);