7

I read an answer here showing how to read an entire stream into a std::string with the following one (two) liner:

std::istreambuf_iterator<char> eos;    
std::string s(std::istreambuf_iterator<char>(stream), eos);

For doing something similar to read a binary stream into a std::vector, why can't I simply replace char with uint8_t and std::string with std::vector?

auto stream = std::ifstream(path, std::ios::in | std::ios::binary);    
auto eos = std::istreambuf_iterator<uint8_t>();
auto buffer = std::vector<uint8_t>(std::istreambuf_iterator<uint8_t>(stream), eos);

The above produces a compiler error (VC2013):

1>d:\non-svn\c++\library\i\file\filereader.cpp(62): error C2440: '' : cannot convert from 'std::basic_ifstream>' to 'std::istreambuf_iterator>' 1>
with 1> [ 1> _Elem=uint8_t 1> ] 1>
No constructor could take the source type, or constructor overload resolution was ambiguous

Community
  • 1
  • 1
Robinson
  • 9,666
  • 16
  • 71
  • 115

2 Answers2

17

There's just a type mismatch. ifstream is just a typedef:

typedef basic_ifstream<char> ifstream;

So if you want to use a different underlying type, you just have to tell it:

std::basic_ifstream<uint8_t> stream(path, std::ios::in | std::ios::binary);    
auto eos = std::istreambuf_iterator<uint8_t>();
auto buffer = std::vector<uint8_t>(std::istreambuf_iterator<uint8_t>(stream), eos);

That works for me.

Or, since Dietmar says this might be a little sketchy, you could do something like:

auto stream = std::ifstream(...);
std::vector<uint8_t> data;

std::for_each(std::istreambuf_iterator<char>(stream),
              std::istreambuf_iterator<char>(),
              [&data](const char c){
                  data.push_back(c);
              });
SirGuy
  • 10,660
  • 2
  • 36
  • 66
Barry
  • 286,269
  • 29
  • 621
  • 977
  • It is quite surprising if that actually works! It _may_ compile but I can't imagine that this runs readily out of the box: creating a stream for a character type other than `char` or `wchar_t` certainly doesn't have to work right away as quite a number of the necessary facets doesn't have to be provided. For the specific need I'd expect that at least `std::codecvt` is missing (I'm not sure if any other facet is really required). – Dietmar Kühl Oct 27 '14 at 14:18
  • If it's not supposed to work out of the box I suppose I shouldn't be doing it. What I'd like is to distinguish between a binary stream and binary data and text stream and text data. So I was going to use uint8_t for binary data and char for text. I suppose it's idiomatic to use char for everything... – Robinson Oct 27 '14 at 14:20
  • You could simply use typedefs like ``typedef std::vector text_vector`` and ``typedef std::vector binary_vector``. While they're the same type under the hood, at least your code becomes self-documenting in terms of what kind of data you expect where. – aruisdante Oct 27 '14 at 14:27
  • Yes, I think I may do that. – Robinson Oct 27 '14 at 14:32
  • 1
    The easiest approach is to just use `std::istreambuf_iterator` to initialize the `std::vector`. – Dietmar Kühl Oct 27 '14 at 14:49
6

ifstream is a stream of char, not uint8_t. You'll need either basic_ifstream<uint8_t> or istreambuf_iterator<char> for the types to match.

The former may not work without some amount of work, since the library is only required to support streams of char and wchar_t; so you probably want istreambuf_iterator<char>.

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644