I am writing a binary version of iostreams. It essentially allows you to write binary files, but gives you great control over the file format. Usage example:
my_file << binary::u32le << my_int << binary::u16le << my_string;
Will write my_int as an unsigned 32-bit integer, and my_string as a string with a prefix of length (where the prefix is u16le.) To read the file back, you can flip the arrows. It works great. However, I hit a punch in the design, and I'm still on the fence about it. So, it's time to ask. (We make a couple of assumptions, such as 8-bit bytes, 2s padding ints, and IEEE floats at the moment.)
iostreams, under the hood, use streambufs. This is a fantastic design - iostreams encodes serialization of " int " into text, and let the underlying streambuf handle the rest. This way you get cout, fstream, stringstream, etc. All this, both iostreams and streambuf, are templates, usually in char, but sometimes also like wchar. My data, however, is a stream of bytes, which is best represented by an " unsigned char ".
My first attempts were unsigned char based class templates. std::basic_string is good enough, but streambuf not. I ran into several problems with a class called codecvt that I could never use for an unsigned char theme. This raises two questions:
1) Why is streambuf responsible for such things? It seems that code conversions are on the side of streambuf's responsibility - streambufs should take a stream and buffer data to / from it. Nothing more. Something as high as the code transforms seems to belong to iostreams.
Since I could not get templated streambufs to work with unsigned char, I returned to char and just cast data between char / unsigned char. I tried to minimize the number of shots for obvious reasons. Most of the data is mostly terminated by the read () or write () function, which then calls the underlying streambuf. (And use the cast in the process.) The read function is basically:
size_t read(unsigned char *buffer, size_t size) { size_t ret; ret = stream()->sgetn(reinterpret_cast<char *>(buffer), size); // deal with ret for return size, eof, errors, etc. ... }
Good decision, bad decision?
The first two questions indicate the need for additional information. First, projects such as boost :: serialization were considered, but they exist at a higher level because they define their own binary format. This is more for reading / writing at a lower level, where you need to determine the format or the format is already defined, or bulk metadata is not required or not required.
Secondly, some asked about the binary::u32le . This is an instance of a class that has the desired consistency and width, currently, possibly signed in the future. The stream contains a copy of the last instance of this class and is used for serialization. This was a bit of a workaround, I initially tried to overload the <statement:
bostream &operator << (uint8_t n); bostream &operator << (uint16_t n); bostream &operator << (uint32_t n); bostream &operator << (uint64_t n);
However, at the time, this did not seem to work. I had several problems with an ambiguous function call. This was especially true for constants, although you could, as one poster suggested, drop or just declare it as const <type> . I seem to remember that there was another big problem.