// these are strictly binary streams. NO conversion of anykind should be done
// at this level
class InputStream {
public:
// Movable
// not copyable.
virtual ~InputStream(){};
/** @returns bytes actually read into buffer */
virtual size_t read(void *data, size_t size)=0;
// like this for no exceptions
template<typename T>
bool read(T &output) noexcept {
return read(&output, sizeof(T)) == sizeof(T);
}
template<typename T>
T read() {
T output;
bool success = read<T>(output);
if(!success){
throw SomeException();
}
}
// maybe have a variadic template that looks like this. I always need to lookup
// the syntax for this. But you get the idea
template<typename ...args>
bool read(T &output, ... args) {
if(read(output)) {
return read(args...);
}
return false;
}
// have the default do nothing in base class. Assumes by default
// there is no buffer. If you want buffer wrap in a BufferedInputStream
virtual void flush(){};
// no close function as this will add some unintuitive expectations.
// If we have close it will need to be virtual. And calling virtual functions
// in destructors is a bad idea.
};
// add specialization by doing something like
template<>
bool InputStream::read<FancyType>(FancyType &output) {
// custom deserialization
return true/false;
}
class OutputStream {
public:
// movable & not copyable
/** @returns bytes actually written into buffer */
virtual size_t write(const void *data, size_t size)=0;
// similar idea as InputStream
virtual void flush()=0;
// similar an InputStream for 2 templates.
};
// for both OutputStream & InputStream have a read/write specialization for std::string
// in the specialization treat it as UTF-8 string by default. And absolutely no analysis of "\r\n" or "\n"
// conversion of anykind. Just read it as is. Just convert UTF-8 to what std::string needs/expects.
// binary is always default
GZipInput<FileInputStream> input(FileInputStream("someFile.gz"));
ifstream file("hello.z", ios_base::in | ios_base::binary);
filtering_streambuf<input> in;
in.push(zlib_decompressor());
in.push(file);
boost::iostreams::copy(in, cout);
for(auto &line : lineReader(input)) {
// do something for each line
}
On 2016–10–21, at 11:05 PM, Nicol Bolas <jmck...@gmail.com> wrote:First, one of the important benefits of the current iostream-based approach is that you can overload stream input/output for specific types (ie: `operator<<` or `operator>>`) without knowing or caring exactly where your data was going. You didn't care if someone used `operator<<` to a file/string/whatever. You just overloaded `operator<<(std::ostream &os, const MyType&)` and you were covered for every kind of output.
That is incredibly important from a usability standpoint. But at the same time, it hurts performance, since it's all based on a virtual interface.
Hi thank you for your feedback. I used it to adapt my ideas further. See bellow. Because of the length and time it took me. I wrote this in markdown, and copied and pasted into the editor in google groups. Which is why it looks different than usual.
Overload << >> for different types without needing to care about underlaying stream class.
InputStreamHolder<>
is for this purpose. It allows a compile time way to anchor templates. There would be an equivalent OutputStreamHolder<>
too for outputstream. Another approach is to create a template like so
template<class T, class POD,
typename
std::enable_if<
std::is_same<
std::streamsize,
decltype(((T*)0x0)->read(nullptr, std::streamsize(0x0)))
>::value
>::type* = nullptr
>
T &operator>>(T &stream, POD &output) {
stream.read(&output, sizeof(POD));
return stream;
}
There well need to be a special class like is_input_stream<>
to make above easier. Reader & Writer will not have read/write method like above. But will have one that accepts a character size and returns a std::string of that many characters not bytes. As reader/writer should be pure character streams.
Runtime flexibility. Trade off between flexibility vs performance
For this I was thinking of having a VirtualInputStreamBase. It would work like so
class VirtualInputStreamBase {
public:
// movable & not copyable
// ...
virtual ~VirtualInputStreamBase(){};
virtual std::streamsize read(void *data, std::streamsize sizeBytes)=0;
};
template<typename T>
class VirtualInputStream : public VirtualInputStreamBase {
public:
VirtualInputStream(T &stream) : mStream(std::move(stream){}
std::streamsize read(void* data, std::streamsize sizeBytes) override {
return mStream.read(data, sizeBytes);
}
private:
T mStream;
};
// now I can do VirtualInputStream<MyVeryCustomStream> stream(MyVeryCustomStream(...));
// I don't know if there is a better way. Either a pointer or reference needs to be used.
std::unique_ptr<VirtualInputStreamBase> someFunction();
// now inputstreams can go beyond with a type-erasure like feature. And something similar to filters
// in boost can be implemented.
Keep basic streams fully binary (no text translations). Text & platform specific translations should be in a higher level filter.
Yes I completely agree with this. The approach I like is having a “Reader” / “writer” classes would handle text & platform specific translations.
IOstream’s stream_buf
has a wide array of use cases. How will these use cases fit into new IOStreams library.
I assume you are referring to basic_streambuf. Breaking down basic_streambuf does the following
I think this is too much for 1 class to handle and should be handled by another class.
Reader<>
/Writer<>
.PeakInputStream<> to handle peaking and putting bytes back. Peaking should work like so
PeakInputStream<SomeStream> stream;
stream.mark(); // mark this spot. Stream will begin recording from this point on
// do some reading and other processing.
stream.markSize(); // essential returns the number of bytes from now to the mark point.
// which means you can go backwards by stream.markSize()
stream.markSeek(stream.markSize()); // go backwards by this amount. This will go back all the way to last mark() call.
Which leads to the obvious usability issue you mentioned. Below is what happens if you want everything.
LocaleReader<BufferedInputStream<PeakInputStream<AsyncInputStream<SomeCustomInputStream>>>> reader;
I think something like the above should still be possible while also allowing the approach done by boost iostreams with its filter. The filter will utilize VirtualInputStream
, or VirtualOutputStream
.
vector::reserve style buffering
Something like a buffered stream? or to tell the OS to preallocate section on the hard drive for a file?
asynchronous streams.
There are read ahead asynchronous IO. This can be done with a AsyncInputStream<> / AsyncOutputStream kind of class. There is also asynchronous io similar to boost ASIO which is pretty much call backs from a users perspective. Under the hood it is very complex. Going the ASIO approach will require classes such as FileHandle, SocketHandle and let the library do any buffering. Boost ASIO style is more powerfull and read ahead can be implemted with a boost ASIO style library. Regardless the case thread executors will be needed. Specifically a thread pool executor.
Looking at boost ASIO. I think it’s good. If it were to become part of the c++ standard an obvious change would be to have it better integrated with c++’s threading library.
Thank you.
I assume you are referring to basic_streambuf. Breaking down basic_streambuf does the following
- I don’t fully understand the rest. To me it seams everything is related to the above.
Can you elaborate bulk I/O? Is ability to disabling buffering enough? Thank you.