
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.