[Boost.Serialization] How to make CString behave like a primitive in xml archive?
Hi! I cannot figure out how to make some class behave like a primitive and let boost::serialization rely on ostream/istream operators for xml input. This makes sense to me for e.g. String representations other than std::string. It seems it does not suffice to define the iostream operators. As an example here is my try to get things right for an MFC/ATL CString: std::istream & operator>>(std::istream &is, CString & S) { std::string s; is >> s; S = s.c_str(); return is; } #ifndef BOOST_NO_STD_WSTREAMBUF std::wistream & operator>>(std::wistream &is, CString & S) { std::wstring s; is >> s; S = s.c_str(); return is; } #endif BOOST_CLASS_IMPLEMENTATION(CString, boost::serialization::primitive_type) Output works fine due to automatic conversion of CString to char* but input fails. Debugging shows the end tag is "eaten" and appended to the value. This means that in struct C { CString SomeString; }; template<class Archive> inline void serialize(Archive & ar, C & c, const unsigned int version) { using boost::serialization::make_nvp; ar & make_nvp("SomeTag", c.SomeString); }; C c; c.SomeString = Test; reading the archive yields SomeString == "Test</SomeTag>" and an exception ("stream error") is thrown by basic_xml_archive. Any hint how to get things fixed here? This would be nice in cases a string represenation is already defined. Or should I work around this with binary_object? And how? regards, Markus
Markus Werle wrote:
Hi!
I cannot figure out how to make some class behave like a primitive and let boost::serialization rely on ostream/istream operators for xml input. This makes sense to me for e.g. String representations other than std::string. It seems it does not suffice to define the iostream operators.
"primitive" types are those supported explicitly by the class basic_?_?primitive with a function save/load in this class. There would be a number of ways of doing what you want depending on the different usage scenarios. The easiest would be just to define non-untrusive serialize templates for these classes. If you want limited functionality (e.g. no tracking, versioning) then assign a lower implementation level - but one higher than primitive. Implemenation can be anything you want. You might just want to lift the implementation for std::string from basic??primitive. or not. primitive is used for bypassing the whole serialization system itself so it presumes direct support in the archive class. If you really want to use primitive, be prepared to make your own archive and or derive from an existing archive - and now your new type will only work on your own archive . Robert Ramey
Robert Ramey
Markus Werle wrote:
Hi!
I cannot figure out how to make some class behave like a primitive and let boost::serialization rely on ostream/istream operators for xml input. This makes sense to me for e.g. String representations other than std::string. It seems it does not suffice to define the iostream operators.
"primitive" types are those supported explicitly by the class basic_?_?primitive with a function save/load in this class.
There would be a number of ways of doing what you want depending on the different usage scenarios.
The easiest would be just to define non-untrusive serialize templates for these classes.
Unfortunately this approach still requires an extra nvp for xml files, so for the code below we obtain e.g. <SomeTag><value>SomeText</value><SomeTag> The extra tag is what I would like to avoid. template<class Archive> inline void save(Archive & ar, CString const & S, unsigned int const /* file_version */) { using boost::serialization::make_nvp; std::string const s(S); ar << make_nvp("value", s); // how to avoid nvp here? } template<class Archive> inline void load(Archive & ar, CString & S, unsigned int const /* file_version */) { using boost::serialization::make_nvp; std::string s; ar >> make_nvp("value", s); S = s.c_str(); } template<class Archive> inline void serialize(Archive & ar, CString & S, const unsigned int file_version) { boost::serialization::split_free(ar, S, file_version); }
If you want limited functionality (e.g. no tracking, versioning) then assign a lower implementation level - but one higher than primitive.
I was not aware of the side effect of limited functionality. This is probably not what I want ... unsure. Can you provide code which avoids the extra tag in xml files and does the same job as for std::string?
Implemenation can be anything you want. You might just want to lift the implementation for std::string from basic??primitive. or not.
primitive is used for bypassing the whole serialization system itself so it presumes direct support in the archive class. If you really want to use primitive, be prepared to make your own archive and or derive from an existing archive - and now your new type will only work on your own archive .
I wished the UI of boost::serialization could provide some mechanism that allows the plug in of types for which a string representation can be generated and evaluated, or in other words: if the following functions are available: std::istream & operator>>(std::istream &is, MyType & T); std::ostream & operator<<(std::ostream &os, MyType const & T); then a single MACRO line should suffice to tell the lib to use these. At least the requirement of an nvp should be removed from the lib then. But maybe I overlooked something here ... Again: is binary object a way to get this? Markus
Markus Werle wrote:
Robert Ramey
writes:
The easiest would be just to define non-untrusive serialize templates for these classes.
Unfortunately this approach still requires an extra nvp for xml files, so for the code below we obtain e.g.
<SomeTag><value>SomeText</value><SomeTag>
The extra tag is what I would like to avoid.
Note that usage of std::string rather than CString will have the same "extra" tag.
If you want limited functionality (e.g. no tracking, versioning) then assign a lower implementation level - but one higher than primitive.
I was not aware of the side effect of limited functionality. This is probably not what I want ... unsure. Can you provide code which avoids the extra tag in xml files and does the same job as for std::string?
As I said - I don't see that std::string elminates the "value" tag. Other primitives e.g. int have their own tags. So the issue of making a type primitive seems to me to be totally unrelated to whether a type is tagged in an xml file. All data elements in an xml archive are tagged and this in enforced by the interface implementation. But the larger answer to your question is that it seems to me that what you want is a variation on the xml_[i/o]archive. Your variation would handle certain types specially - thereby skipping the tagging for certain types. This can be implemented by making your own derivation from xml_[i/o]archive. Examples of making derivation of an existing archive can be found in the documentation and demos.
I wished the UI of boost::serialization could provide some mechanism that allows the plug in of types for which a string representation can be generated and evaluated, or in other words: if the following functions are available:
std::istream & operator>>(std::istream &is, MyType & T); std::ostream & operator<<(std::ostream &os, MyType const & T);
what's the matter with adding to MyType a common string cast operator and using: template<class Archive> void save(Archive &ar, const MyType &t, const unsigned version){ std::string s = t; ar << t; } and std::ostream & operator<<(std::ostream &os, MyType const & T){ std::string s = t; *this << t; } If you don't like the temporaty string you can make a converter which generates an iterator over the string representation of MyType and uses this same iterator for both stream output and serialization. Robert Ramey
Robert Ramey
Markus Werle wrote:
Robert Ramey
writes: The easiest would be just to define non-untrusive serialize templates for these classes.
Unfortunately this approach still requires an extra nvp for xml files, so for the code below we obtain e.g.
<SomeTag><value>SomeText</value><SomeTag>
The extra tag is what I would like to avoid.
Note that usage of std::string rather than CString will have the same "extra" tag.
I disagree. Probably I did not explain my intention/problem precise enough. To store a std::string in an xml file you need 1 tag, not 2 like for CString. You store it by using std::string myStdString = "Test"; [...] ar & make_nvp("SomeTag", myStdString); and obtain an xml file with a line <SomeTag>Test<SomeTag> which is exactly what I want. But now I want this for any type which has a string representation, e.g. CString from MFC (the lib I hate most) As I showed in a previous post the classical approach, which you also mention in modified form bails out due to missing _extra_ nvp! Compiler fails on template<class Archive> inline void save(Archive & ar, CString const & S, unsigned int const /* file_version */) { using boost::serialization::make_nvp; std::string const s(S); ar << s; } with [...]basic_xml_oarchive.hpp(86) : error C2027: use of undefined type 'boost::STATIC_ASSERTION_FAILURE<> etc. even if you store the CString using an nvp like in CString myStdString = "Test"; [...] ar & make_nvp("SomeTag", myStdString); So there is an unnecessary (?) asymmetry between CString and std::string.
what's the matter with adding to MyType a common string cast operator
and using:
template<class Archive> void save(Archive &ar, const MyType &t, const unsigned version){ std::string s = t; ar << t; } and
std::ostream & operator<<(std::ostream &os, MyType const & T){ std::string s = t; *this << t; }
This is what I tried with CString. My approach yields an xml file with a line containing <SomeTag><value>Test</value><SomeTag> using template<class Archive> inline void save(Archive & ar, CString const & S, unsigned int const /* file_version */) { using boost::serialization::make_nvp; std::string const s(S); ar << make_nvp("value", s); } I still cannot see how to avoid this extra <value> tag. Did it become clear now? Markus
Markus Werle wrote:
I still cannot see how to avoid this extra <value> tag. Did it become clear now?
OK - I think I see the problem now.
There is a solution - but it's slightly tricky.
a) study the document and example on inheriting
from an existing archive.
b) derive your new archive extended_xml_oarchive
from xml_oarchive_impl
c) This will look something like:
class extended_xml_oarchive :
public xml_oarchive_impl
participants (2)
-
Markus Werle
-
Robert Ramey