2

I'm trying to store my struct into txt file using boost but unable to do it. I'm using boost library. example structure

struct Frame
{
    uint32_t address{ 0 };
    uint16_t marks{ 0 };
    uint16_t age{ 0 };
    char gender{ 'M' };
    std::string userName;
};

for binary there is simple code

boost::archive::binary_oarchive ar(ofs, boost::archive::no_header);
ar << boost::serialization::make_binary_object(&f, sizeof(Frame));

assume file is open with fstream object ofs and 'f' is object of 'Frame'

I want to know if there is similar way to write structure to txt file and I do not want to write data types one by one. assume we don't know the types/number of datatypes inside structure.

Rav
  • 75
  • 9
  • 1
    Since your `Frame` isn't trivially copyable, it must be written with multiple writes to the stream. If you don't want to do it yourself, you can use a library like the one you use now. By "_similar_", do you mean another library or what do you mean exactly? – Ted Lyngmo Aug 23 '21 at 08:41
  • I mean in this same boost library if there is similar function for text storage just like binary serialization. – Rav Aug 23 '21 at 08:47
  • @Ted in above example what I'm doing is using make_binary_object and then serializing it. I want to know if there is similar function for text also. – Rav Aug 23 '21 at 08:55
  • Did you read the [documentation](https://www.boost.org/doc/libs/1_76_0/libs/serialization/doc/archives.html) on archives? Is there any reason not to use `text_oarchive`? However, note that writing `std::string` as a sequence of bytes is totally meaningless. – Evg Aug 23 '21 at 08:56
  • @Evg I'm a beginner and I'm using text_oarchive but after that I don't know what to use for text to write it into text file because boost::serialization::make_binary_object won't work as it is for binary. I want to write whole structure at once similar to what I'm doing it with boost::serialization::make_binary_object, is there any similar function for text also? I want to know that because I'm unable to find it. – Rav Aug 23 '21 at 09:37
  • It is not clear what you mean by "text". `make_binary_object()` is meaningful only for structs of primitive types that have no "internal" structure. Your code example is wrong. – Evg Aug 23 '21 at 09:51
  • @Evg so you're saying that we can't use `make_binary_object()` for binary serialization for structure containing string? because the example above is for binary... and I want to read and write for struct containing `std::string` in both binary and text format(that's what I meant by 'text') using `boost::serialization`. – Rav Aug 23 '21 at 10:15
  • No, we can't. It might appear to work with short strings because short strings are kept inside `std::string` object itself (google SSO - short string optimization), but it will fail for long enough string, which are allocated dynamically. Think of it: `sizeof(Frame)` does **not** depend on the `userName` length. In both cases it will be UB anyway. See the first comment by Ted Lyngmo. – Evg Aug 23 '21 at 10:59
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/236314/discussion-between-ravi-and-evg). – Rav Aug 23 '21 at 12:19

1 Answers1

1

As others have commented you will have to provide serialization helpers that tell Boost how to member-wise serialize.

If you have only aggregates like this, you can automate the generation of this function to a degree with Boost PFR:

pfr::for_each_field(s, [&](auto&& f) { ar & f; });               

Here's an example:

namespace MyLib {
    struct Frame {
        uint32_t    address{0};
        uint16_t    marks{0};
        uint16_t    age{0};
        char        gender{'M'};
        std::string userName;
    };

    struct Other {
        std::string userName;
        std::map<uint32_t, std::string> properties;
    };

} // namespace MyLib

Note I stick them in a namespace for good style and also so we can highlight that ADL is used to find a serialize overload. Now let's define the overloads:

namespace MyLib {
    #define SERIALIZER(Aggregate)                                                  \
        template <typename Archive>                                                \
        void serialize(Archive& ar, Aggregate& s, unsigned version)                \
        {                                                                          \
            pfr::for_each_field(s, [&](auto&& f) { ar & f; });               \
        }


    SERIALIZER(Frame)
    SERIALIZER(Other)
} // namespace MyLib

Using the macro we avoid repeated code. Of course you could do this without a macro as well.

Now you can serialize both. Let's say we have:

MyLib::Frame const diablo{178, 42, 37, 'F', "Diablo"};
MyLib::Other const other{"diablo", {{1, "one"}, {2, "two"}, {3, "three"},}};

Then serializing to a text stream:

boost::archive::text_oarchive oa(ss);
oa & diablo;
oa & other;

Already results in the stream containing e.g.

22 serialization::archive 19 0 0 178 42 37 70 6 Diablo 0 0 6 diablo 0 0 3 0 0
 0 1 3 one 2 3 two 3 5 three

Full Demo

This demo checks that the result of deserializing is actually identical to the original structs:

Live On Coliru

#include <string>
#include <map>

namespace MyLib {
    struct Frame {
        uint32_t    address{0};
        uint16_t    marks{0};
        uint16_t    age{0};
        char        gender{'M'};
        std::string userName;
    };

    struct Other {
        std::string userName;
        std::map<uint32_t, std::string> properties;
    };

} // namespace MyLib

#include <boost/pfr.hpp>
namespace pfr = boost::pfr;

namespace MyLib {
#define SERIALIZER(Aggregate)                                                  \
    template <typename Archive>                                                \
    void serialize(Archive& ar, Aggregate& s, unsigned version)                \
    {                                                                          \
        pfr::for_each_field(s, [&](auto&& f) { ar & f; });               \
    }

    SERIALIZER(Frame)
    SERIALIZER(Other)
} // namespace MyLib

#include <iostream>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/map.hpp>

int main() {
    MyLib::Frame const diablo{178, 42, 37, 'F', "Diablo"};
    MyLib::Other const other{"diablo", {{1, "one"}, {2, "two"}, {3, "three"},}};

    std::stringstream ss;
    {
        boost::archive::text_oarchive oa(ss);
        oa << diablo;
        oa << other;
    }

    std::cout << ss.str() << "\n";

    {
        boost::archive::text_iarchive ia(ss);
        MyLib::Frame f;
        MyLib::Other o;

        ia >> f >> o;

        std::cout << std::boolalpha;
        std::cout << "Frame identical: " << pfr::eq(diablo, f) << "\n";
        std::cout << "Other identical: " << pfr::eq(other, o) << "\n";
    }
}

Prints

g++ -std=c++2a -O2 -Wall -pedantic -pthread main.cpp -lboost_serialization && ./a.out
22 serialization::archive 19 0 0 178 42 37 70 6 Diablo 0 0 6 diablo 0 0 3 0 0 0 1 3 one 2 3 two 3 5 three

Frame identical: true
Other identical: true
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Hi I tried doing this. Idk why my boost lib do not have pfr.hpp, do I have to add it separately to boost, if so then help me with that. And also this Aggregate& s is giving error that it is not defined. – Rav Aug 24 '21 at 09:32
  • PFR was [introduced in Boost 1.75.0](https://www.boost.org/users/history/version_1_75_0.html). You can do it without, of course: http://coliru.stacked-crooked.com/a/b5a39df8f2dfd2f3, you can use your own bespoke macro: http://coliru.stacked-crooked.com/a/cbda0edbdf6073e3 but I'd just keep the manual version in this case – sehe Aug 24 '21 at 12:33
  • (PS that second macro wizardry generates http://coliru.stacked-crooked.com/a/114505b9be18306f effectively) – sehe Aug 24 '21 at 12:35