5

I want to be able to use an ostream_iterator to stream to a binary file. But the ostream_iterator uses a FormattedOuputFunction so it will write ASCII, not binary:

std::ostream_iterator is a single-pass OutputIterator that writes successive objects of type T into the std::basic_ostream object for which it was constructed, using operator<<

Beyond writing my own iterator is there a way to use an iterator to write binary?

A simplified example of what I'm trying to do, but the copy statement is going to write ASCII to my binary file:

ofstream foo("foo.txt", ios_base::binary);
vector<int> bar = {13, 42};

copy(bar.cbegin(), bar.cend(), ostream_iterator<decltype(bar)::value_type>(foo));
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288

3 Answers3

5

ostreambuf_iterator is more appropriate than ostream_iterator. It's much lighter weight and it does no formatting. It takes a template argument for the character type, so the only choice compatible with most streams is std::ostream_iterator< char >.

Be sure to open the stream in binary mode. The standard stream buffers, by the way, are never opened in binary mode.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • I looked every where for this. Even wrote a [hackish answer](http://stackoverflow.com/a/32073224/2642059) to get a solution. This is the correct answer. I'll accept after I leave some time for this answer to garner upvotes. – Jonathan Mee Aug 18 '15 at 13:10
  • @JonathanMee If you like :) . Be careful when unaccepting answers, though; if I hadn't edited after the unaccept you would never be able to accept it again. – Potatoswatter Aug 18 '15 at 13:11
  • Ugh, it looks like I still need my hackish answer. `ostreambuf_iterator` only accepts `char`s. Meaning that like my answer this won't work for anything but [InputIterator or ForwardIterator](http://en.cppreference.com/w/cpp/iterator#Iterator_categories), or you'll have to also use a wrapper or specialized iterators. – Jonathan Mee Aug 18 '15 at 13:15
  • @JonathanMee Iterators by definition work with only one type. It sounds like you want a binary serialization library. You do want formatting, just not into text. There are various approaches, but you might ask a more specific question. – Potatoswatter Aug 18 '15 at 13:22
  • The question asks for an iterator that works with standard algorithms. Algorithms like `reverse_copy` or `partial_sort_copy` are disaster for this answer or my hack. – Jonathan Mee Aug 18 '15 at 13:31
  • @JonathanMee You can't read a file randomly or in reverse. You can write code to do so, but it will be horribly inefficient since essentially every `seek` operation goes to the kernel. Copy the data into memory, then process it with ordinary iterators (pointers, even). – Potatoswatter Aug 18 '15 at 13:46
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/87278/discussion-between-jonathan-mee-and-potatoswatter). – Jonathan Mee Aug 18 '15 at 13:51
2

It works, but you will have to explicitely use an ostream_iterator<char>.

Example (includes omitted for brievety):

int main(int argc, char **argv) {
    std::vector<int> arr;

    std::ofstream fd("foo.txt", std::ios::binary | std::ios::out);

    for (int i=0; i<256; i++) arr.push_back(i);

    std::ostream_iterator<char> oi(fd);
    std::copy(arr.begin(), arr.end(), oi);
    fd.close();
    return 0;
}

will write the 256 bytes for 0 to 255 in foo.txt.


Above assumed that you wanted to write directly the value of the int as a char to the file. From your comment, you want to write the int value as a 4 bytes value (assuming int32_t) in native host endianness. I would use an auxilliary class to do the job:

class bint {
private:
    char c[sizeof(int)];

public:
    bint(const int i) { // allows bint b = 5;
        ::memcpy(c, &i, sizeof(c));
    }
    operator int() const {  // allows : bint b = 5; int i=b => gives i=5
        int i;
        ::memcpy(&i, c, sizeof(int));
        return i;
    }
    const char *getBytes() const { // gives public read-only access to the bytes
        return c;
    }
};

std::ostream& operator <<(std::ostream& out, const bint& b) {
    out.write(b.getBytes(), sizeof(int));
    return out;
}

int main(int argc, char **argv) {
    std::vector<int> arr;

    std::ofstream fd("foo.txt", std::ios::binary | std::ios::out);

    for (int i=0; i<256; i++) arr.push_back(i);

    std::ostream_iterator<bint> oi(fd);
    std::copy(arr.begin(), arr.end(), oi);
    fd.close();
    return 0;
}

This one writes 1024 bytes (for integer of size 4) containing the representations of the 256 first integers. It would automatically adapts to other sizes of int.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • My heart leaped when I saw this. But alas it does not work. It simply casts each `int` to a `char` and then outputs that ASCII char to the stream. [This example](http://coliru.stacked-crooked.com/a/c8c945a72877458f) shows the result: `"wrong value 1: 842281777 wrong value 2: 0 wrong value 3: 0 wrong value 4: 0 right value 1: 49 right value 2: 51 right value 3: 52 right value 4: 50"` – Jonathan Mee Aug 17 '15 at 17:38
  • @JonathanMee: I thought that was what you expected. What result do you want for `{13, 42}`? – Serge Ballesta Aug 17 '15 at 17:47
  • I am writing to integers in binary format. When I read two integers in binary format I expect them to be the same round trip, so I expect 13, 42. In the example I linked you can see how `read` and `write` accomplish this correctly. Giving me an unchanged round trip. – Jonathan Mee Aug 17 '15 at 17:52
  • 1
    @JonathanMee: I've understood. I must think again to my solution... I should give you something in a while – Serge Ballesta Aug 17 '15 at 17:54
  • Interesting a wrapper class... I hadn't considered that angle of attack. – Jonathan Mee Aug 17 '15 at 19:02
  • After a brief and exciting consideration of `ostreambuf_iterator` I am again forced to accept this is the only option within the standard (beyond writing my own iterator.) I'll again state that if only an [InputIterator or ForwardIterator](http://en.cppreference.com/w/cpp/iterator#Iterator_categories) are needed for the algorithm inputs [my solution](http://stackoverflow.com/a/32073224/2642059) is a better choice. – Jonathan Mee Aug 19 '15 at 10:53
  • Also this answer could be improved by [making `c` a reference](http://chat.stackoverflow.com/transcript/message/25158901#25158901) and by using a `ostreambuf_iterator` instead of an `ostream_iterator`. – Jonathan Mee Aug 19 '15 at 10:54
1

For algorithms that only use an InputIterator or ForwardIterator for the input, a simple cast is sufficient. For more complex algorithms writing a wrapper, writing a specialized iterator, or using Boost functionality may be necessary. Provided the algorithm input aligns with those conditions, something like this will work:

ofstream foo("foo.txt", ios_base::binary);
vector<int> bar = {13, 42};

copy(reinterpret_cast<const char*>(&*bar.cbegin()), reinterpret_cast<const char*>(&*bar.cend()), ostreambuf_iterator(foo));

Obviously this needs to be round trip certified before it can be considered dependable. Validating that the values in output are consecutive can be tedious so code was hijacked from here to do that:

ofstream foo("foo.txt", ios::binary);
vector<int> bar(numeric_limits<unsigned char>::max() + 1);

iota(bar.begin(), bar.end(), 0);

copy(reinterpret_cast<const char*>(&*bar.data()), reinterpret_cast<const char*>(&*bar.data() + bar.size()), ostreambuf_iterator<char>(foo));
foo.close();

ifstream file_read("foo.txt", ios::binary);
vector<decltype(bar)::value_type> output(bar.size());

copy(istreambuf_iterator<char>(file_read), istreambuf_iterator<char>(), reinterpret_cast<char*>(&*output.data()));

cout << "First element: " << output.front() << "\nLast element: " << output.back() << "\nAny non-consecutive elements: " << (output.cend() == mismatch(output.cbegin(), prev(output.cend()), next(output.cbegin()), [](auto first1, auto first2) { return first1 + 1 == first2; }).second ? "no\n" : "yes\n");

The output from this demonstrates that this method is actually successful:

First element: 0
Last element: 255
Any non-consecutive elements: no

Although not every possible int was tried, every possible char was tried, and since any int can be made up of a collection of chars this demonstrates that any collection of ints is streamable this way.

Community
  • 1
  • 1
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • 1
    The standard does require the a char has the weaker alignement. 3.11 Alignement §6 (from standard C++11 draft n4296) says: *the narrow character types shall have the weakest alignment requirement. [ Note: This enables the narrow character types to be used as the underlying type for an aligned memory area -- end note]*. That implies that `sizeof(T)` must be a multiple of `sizeof(char)` – Serge Ballesta Aug 18 '15 at 13:15
  • @SergeBallesta Thanks, I didn't know that. I've edited accordingly. – Jonathan Mee Aug 19 '15 at 11:12