17

I recall once seeing a clever way of using iterators to read an entire binary file into a vector. It looked something like this:

#include <fstream>
#include <ios>
#include <iostream>
#include <vector>

using namespace std;

int main() {
    ifstream source("myfile.dat", ios::in | ios::binary);
    vector<char> data(istream_iterator(source), ???);
    // do stuff with data
    return 0;
}

The idea is to use vector's iterator range constructor by passing input iterators that specify the entire stream. The problem is I'm not sure what to pass for the end iterator.

How do you create an istream_iterator for the end of a file? Am I completely misremembering this idiom?

Adrian McCarthy
  • 45,555
  • 16
  • 123
  • 175

2 Answers2

32

You want the std::istreambuf_iterator<>, for raw input. The std::istream_iterator<> is for formatted input. As for the end of the file, use the stream iterator's default constructor.

std::ifstream source("myfile.dat", std::ios::binary);
std::vector<char> data((std::istreambuf_iterator<char>(source)),
                       std::istreambuf_iterator<char>());

Edited to satisfy C++'s most vexing parse. Thanks, @UncleBens.

wilhelmtell
  • 57,473
  • 20
  • 96
  • 131
11

In C++11 one could:

std::ifstream source("myfile.dat", std::ios::binary);
std::vector<char> data(std::istreambuf_iterator<char>(source), {});

This shorter form avoids the most vexing parse problem because of the {} argument, which removes ambiguity of it being an argument or a formal parameter.

@wilhelmtell's answer could also be updated to avoid this problem by adopting a brace initializer for data. Still in my view, using {} is more simple and turn the initialization form irrelevant.

EDIT

Or, if we had std::lvalue (and maybe std::xvalue instead of std::move):

#include <vector>
#include <fstream>

template <typename T>
constexpr T &lvalue(T &&r) noexcept { return r; }

int main() {
    using namespace std;

    vector<char> data(
        istreambuf_iterator<char>(lvalue(ifstream("myfile.dat", ios::binary))),
        {}
    );
}
oblitum
  • 11,380
  • 6
  • 54
  • 120
  • 1
    Hi @pepper_chico. Am I correct in thinking that when you use `{}` in the second argument for the data constructor the compiler determines that the type of this argument should be `std::istreambuf_iterator` and therefore calls its default constructor `std::istreambuf_iterator{}`? Is that what `{}` is expanded to? – Stan Dec 02 '17 at 14:40
  • 2
    @Stan Yes, it does the same as `std::istreambuf_iterator()`, it's an useful construction, works like `auto`, but for values (default values) instead of types alone. – oblitum Dec 02 '17 at 14:44