Boost logo

Boost :

Subject: Re: [boost] [convert] Now with Boost.Parameter interface.
From: Vladimir Batov (vladimir.batov_at_[hidden])
Date: 2009-07-08 19:52:06


> Hartmut Kaiser <hartmut.kaiser <at> gmail.com> writes:
>
> > int i = convert<int>::from(str, 0)(locale_ = new_locale)(throw_=true);
> > int i = convert<int>::from(str, 0) >> new_locale >> dothrow;
>
> Sorry if I'm late here. Either of both ways just feels wrong as not all of
> the manipulators/parameters are of general use. The radix_ parameter
> described elsewhere is a good example for a parameter applicable to string
> <--> integer conversion only. It is highly confusing not to get a compile
> time error while specifying a wrong parameter (a parameter not being
> applicable for the type to convert to/from).
>
> For instance, what is supposed to happen if I do:
>
> convert<double>::from(str, radix_ = 16)

Well, I agree that "it is highly confusing not to get a compile time error while
specifying a wrong parameter". Fortunately, it does not have to be that way and
is up to the conversion implementer. Say,

convert<double>::from(str)(radix_ = 16)
convert<double>::from(str) >> std::hex

Both above internally deploy a specialization of some converter<TypeIn,
TypeOut>. Then, additional formatting (like op() for 'radix_=16' or op>> for '>>
std::hex') is passed to *that* converter. That is the #1 above can be seen as

converter<std::string, double> cvt = convert<double>::from(str);
cvt(radix_ = 16);
double d = cvt.operator double();

It's all up to the implementer of converter<std::string, double> to accept or
reject 'radix_=16' during *compilation*.

> The current approach in my view mixes things not belonging together. A
> conversion library needs to keep apart data and formatting information. This
> is not done here. Formatting and data type are spread over the whole
> expression, making it very difficult to get things right.

Well, I probably cannot fully agree with "formatting and data type are spread"
as it is always data first, then the rest. Say, my typical usage is

convert<double>::from(str)
>> locale
>> std::scientific;

the first line has the data and the rest is about formatting. Even in

convert<double>::from(str)(locale_=locale) >> std::scientific;

it seems clear that the data comes first with everything else following.

As for "conversion library needs to keep apart data and formatting" I think in
'convert' data and formatting are apart due to the conversion specification
*order*. Your preference (as I understand) is to take that separation further
(as in Spirit) physically separating the formatting into a separate object.

> I believe this is a strength of Spirit's approach, where data and formatting
> are clearly separated.

Well, I'd insist on just :-) "clearer separated" as I feel 'convert' does
provide that separation although not to the degree Spirit does. And here I
suspect we are talking about *visual* separation as under the hood 'convert' can
be as strict (data-wise and formatting-wise) as the implementer likes.

> Applying a similar approach to convert would result
> in:
>
> double d = convert::from(str, double_);
> and
> std::string s = convert::to(d, double_);
>
> where double_ is an example for a placeholder...

Please bear with me as I cannot claim familiarity with Spirit. I am not sure
if/how that placeholder-based approach can be uniformly extended onto user
types. I'd expect something like

direction d = convert::from(str, direction_);

That is, for a 'direction' user type to be integrated into the 'convert'
framework, the user needs do provide a 'direction_' specification. If so, then I
have no serious objections against that. It's (I think) in line with what
Vicente was suggesting with convert_traits and it is *somewhat* similar to the
current lexical_cast/convert approach. Although lexical_cast/convert limit that
"specification" to only op>> and op<<.

> Spirit allows to build more complex formats/grammars out
> of simpler ones, while providing all necessary primitive type conversions).
> For instance, applying a different locale to this results in:
>
> real_spec<double> locale_double(new_locale);
> double d = convert::from(str, locale_double);
> and
> std::string s = convert::to(d, locale_double);
>
> where real_spec (choose a different name, if you like) creates a new
> placeholder encapsulating all formatting information needed to do the
> conversion.

Well, I feel you are arguing the interface -- forcing the user to provide the
formatting as a separate fmt object as the following could easily have the same
implementation.

real_spec<double> locale_double(new_locale);
double d = convert::from(str, locale_double);
or
double d = convert::from(str, real_spec<double>(new_locale));
or
double d = convert::from(str)(real_spec<double>(new_locale));
or
double d = convert::from(str).locale(new_locale);
or
double d = convert::from(str)(locale_=new_locale);

As for the interface, your approach might well be justified for Spirit
applications. From *my* usage experience of 'convert' I feel for 'convert' it'd
be an overkill. Please bear in mind that lexical_cast/convert never had
Spirit-like ambitions and should be never treated as a serious converter of any
sort. That does not mean, I do not feel 'convert' has no place. If you like
'convert' is a bike to get a short distance from A to B quickly and without
much/any preparation. Spirit is for a longer trip. My usage pattern is to get a
dozen or more config. parameters, convert them to their binary form, report
conversion failures and get to my main domain-specific (non-parsing-related)
business. Therefore, I am only prepared to allocate 12 lines of code to convert
12 parameters and to get to my main business ASAP.

> This has the additional advantage of providing a nicely extensible
> framework. Any non-foreseeable formatting requirements are easily
> customizable.

I honestly see neither of 'convert' or Spirit approaches easier/harder to
customize. That customization has to be implemented somewhere. Spirit (as I
understand) does it via that "formatting specification" object. 'convert' has
the converter.

In fact, come to think of it now I not sure how real_spec<double> can help as
TypeOut ('double' in the example) is only half of the story. Formatting equally
depends on TypeIn. Say, I do not think we can allow/disallow (radix_=16) for
real_spec<int> as we do not yet know if we are converting from a string.

> OTOH, general parameters applying directly to the conversion process (such
> as throwing behavior or default values) are fine to be passed as additional
> arguments. But care must be taken not to intermix this with formatting
> related parameters (and yes, believe me or not, locales _are_ formatting
> related). So I think the following would be ok:
>
> double d = convert::from(str, double_, default_ = 0.0);
> and
> std::string s = convert::to(d, double_, throw_ = true);

Don't you think I can apply the following *interface* transformation without
undermining the actual implementation:

double d = convert::from(s, double_formatter, default_ = 0.0);
to
double d = convert<double>::from(s, default_=0.0)(double_formatter);
to
double d = convert<double>::from(s,0)(double_formatter);
to
double d = convert<double>::from(s,0)(double_fmt_part1)(double_fmt_part2);

which the current interface. ;-)

> BTW: this interface above is easily implementable on top of Spirit. It's
> just a thin wrapper around existing functionality. And I already expressed
> my opinion that any high level conversion library should be built on top of
> Spirit...

I do not think anyone even argued against that. And I do not feel that hte
existing interface stops us from dong that.

>... - a proven, fast, reliable, versatile, and mature parser and
> generator framework.

:-)

> Anything else leads to code duplication,
> inconsistencies, implementation flaws, you name it.

Well, I personally do not see code duplication, etc. what I see is different
horses for different courses. With all due respect as of now I see Spirit as too
big a hammer for my purposes.

> Converting a string of
> digits into an integer might seem to be an easy task. But it's not. It's
> tricky, error prone, and difficult to get correct _and_ fast at the same
> time.

I surely agree. That said, in many many cases people do not need that complexity
and processing power. lexical_cast (and convert -- a glorified lexical_cast) is
far from perfect. However, its functionality (and often even speed) is
sufficient (well, almost) and it's better known in average-user circles and
easier to understand and deploy. I spend my day writing fuzzy logic related to
train movements. I only care about conversions as much as reading from a config.
file or reading inter-process XML. For that as of now I find Spirit too steep a
ladder to claim for my current needs. It's not a criticism of any kind, just how
I feel.

V.


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk