I am not arguing against interface/behavior specifications (a.k.a. "contracts") for converters. I only feel that "convert"'s purpose is far more modest -- to only specify how to deploy various converters and what to expect when we do. It seems far beyond my capabilities and expertise to come up with "contracts" you listed.
OK, then I would expect the contract for a highly-generic converter to pose minimal requirements on the Types to convert, preferably none.
The user still needs to supply a Default Construction function it is
just
not the default constructor.
I feel it's a generic converter requirement -- they all need a "place" to write the result to. We might say -- every converter for itself. On the other hand, that seemed like a generic enough requirement that the "framework" might care providing uniformly to every converter.
It is not a generic converter requirement IMO. Some converters might just create their own place. As the example that I gave.
Well, I feel you are not avoiding requirements but merely shifting them around from boost::convert() down to converters. A converter will still need to be able to create and TypeOut instance somehow and in a generic way. So, I feel, we'll be back where we are now with the burden of every converter having to do it itself.
The specification of requirements is shifted down because some converters have the requirement and others not.
I was not able to come up with a better implementation that would not
have
those requirements. If you have such an implementation, I'll incorporate it gladly.
That seems like a 0-gain game as we still have all the same requirements and all the same code, right?
No the requirements are not the same. Using the boost::optional from master the only requirement for the large_vector_converter TypeOut is that it is MoveConstructible. In other words it is possible to have the same interface without requiring DefaultConstructible (indirect). Moreover, even though it is required to be MoveConstructible in practice the MoveConstructor will not be invoked because of return value optimization: the large_vector is only constructed once. Still the requirement of MoveConstructible is not strictly necessary and you could write your result type in to not require it (something more like std::unique_ptr than boost::optional).
We still need to be able to create a TypeOut instance somehow generically.
Well, not generically, the actual converters can be very specific.
Although the code above forces every converter to do that by themselves, right? Although passing boost::optional into a container might have its benefits... I had it at one point... will have to re-visit again.
The benefit is that it defers initialization and thus passes that responsibility from the generic function to the specific converters.
That's something I'll have to think about. What jumps at me though is what I'd call "blur of responsibilities" -- now converter not only cares for conversion but for allocation as well. Secondly, in the current "convert" design converters know nothing about boost::convert "framework". The implementation above seems to require the converter to know/deploy boost::converter_default_maker which is either part of the "convert" infrastructure or converter's own code, i.e. the converter has to have either additional "knowledge"/coupling or additional code. In both cases I do not like the "additional" bit. :-)
Some converters require this additional code/knowledge and others not. For instance, your encryption converter would probably not need it. On a completely different note. In the cstringstream_converter you may want to check that the stream_.str() is empty after conversion. For instance, would you like the following to return 7 or 999? return boost::convert<int>::from("7 + 5", cnv).value_or(999); If you check(and clear) after conversion, you probably do not need to clear before conversion, so there may be no penalty at all?