J
Joshua Maurice
I've always found the C++ std iostreams interface to be convoluted,
not well documented, and most non-standard uses to be far out of the
reach of novice C++ programmers, and dare I say most competent C++
programmers. (When's the last time you've done anything with facets,
locales, etc.?)
I've never been a fan of parallel duplicate class hierarchies. It's a
huge design smell to me. The C++ standard streams have this design
smell. They have ifstream and the associated file streambuf,
stringstream its associated string streambuf, etc. The design smell
tends to indicate duplicate code and an overly complex set of classes.
If each class appears as a pair, why have two separate hierarchies?
Also, I've always liked C++ templates as compile time polymorphism. I
would think it natural to do something like create a stream which
writes to a file, then put a buffering wrapper over that, then put a
formatter over it to change '\n' to the system's native line ending,
then put a formatter over that whose constructor takes an encoding
(eg: UTF 8, ASCII, etc.) and whose operator<< functions take your
unicode string and converts it the encoding passed in the constructor.
The current std streams allow you to do this (sort of), but it's much
more complicated than what it needs to be, and it's done with runtime
polymorphism, not compile-time polymorphism of templates, so
potentially much slower.
Also, the std streams internationalization support is at best
pisspoor. The existence of locales and their meanings are
implementation defined. One cannot rely upon any of the C++ standard
locale + facet stuff for a portable program. It's also entirely
convoluted and complex, and doesn't support simple things like
changing from one encoding to another. Now, in the standard
committee's defense, internationalization is hard (tm). However, I
wish they did not try at all rather than clutter up a good standard
library with nearly useless features like locales and facets. Also,
seriously, wchar_t's size is implementation defined? Why even bother?
The new fixed size types for UTF 8, UTF 16, and UTF32 is a step in the
right direction, but from what little I understand it's still coming
up quite short. (Ex: std::string still will not support variable width
encodings like UTF 8 and UTF 16, but it will bear a name of UTF 8 and
UTF 16, a huge misnomer and disservice.)
As a start, I would suggest the following code. Now, I admit that I'm
not the most familiar with C++ std streams, but I think I know enough
to make the critics in this post. I'm just wondering if something like
the below code could not support something which the current C++ std
streams do support? And any other comments on the viability and
usability of the interface and implementation below?
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string>
namespace jjm
{
#ifdef WIN32
#pragma warning (disable : 4800)
#endif
/*
Three flags, eofbit, failbit, badbit.
eofbit is set whenever end of file (or stream, std::string, etc)
is encountered.
failbit is set whenever the operation could not complete
successfully:
ex: failure to open a file
ex: getline called and no characters left in the stream
ex: not set when getline is called with characters left in the
stream,
even if the deliminating character is not found
badbit is set when some underlying system call failed for an
"uncommon",
"unexpected", "not normal course of operations" reason:
ex: Not because of end of file, nor file doesn't exist, etc.
Some streams are wrappers over other streams. When a stream X has
been
wrapped by a stream Y, do not use stream X again for reading or
writing,
as stream Y may have read ahead from X and cached it, or Y may be
caching
data to write to X and not written it yet.
*/
/*DO NOT USE THE INTEGER VALUES DIRECTLY!
USE THE ENUM NAMES ONLY!*/
enum { eofbit = 1, failbit = 2, badbit = 4 };
/*To facilitate a safer conversion in a if / for / while / etc
condition
DO NOT USE THESE NAMES AT ALL (outside of this implemention)!*/
struct condition_conversion_class { void
condition_conversion_function() {} };
typedef void (condition_conversion_class::*condition_conversion)
();
/*each istream should have these verbatim*/
#define JJM_ISTREAM_COMMON_FUNCTIONS \
operator condition_conversion () const \
{ return (rdstate() & (failbit | badbit)) \
? 0 \
: &condition_conversion_class \
::condition_conversion_function; \
} \
bool operator! () const \
{ return (rdstate() & (failbit | badbit)) \
? true \
: false; \
} \
bool good() const { return ! rdstate(); } \
bool eof() const { return rdstate() & eofbit; } \
bool fail() const { return rdstate() & failbit; } \
bool bad() const { return rdstate() & badbit; } \
void setstate(unsigned char or_state) { clear(rdstate() |
or_state); }
class istream_wrapper
{
private:
template <bool b> struct static_assert_struct_template {};
#define JJM_STATIC_ASSERT(cond) \
typedef static_assert_struct_template<true> \
static_assert_typedef; \
typedef static_assert_struct_template<cond> \
static_assert_typedef;
class wrapper_base
{
public:
wrapper_base(void* stream_) : stream(stream_) {}
virtual ~wrapper_base() {}
virtual int get() = 0;
virtual size_t read(char* s, size_t const n) = 0;
virtual void clear(unsigned char new_state = 0) = 0;
virtual unsigned char rdstate() const = 0;
virtual void cloneInPlace(void* ptr) const = 0;
void swap(wrapper_base& x) { std::swap(stream,
x.stream); }
protected:
wrapper_base() {}
void* stream; /*declared in base class to do sizeof
(wrapper_base) later in code*/
private:
wrapper_base(wrapper_base const& ); /*not defined, not
copyable*/
wrapper_base& operator= (wrapper_base const& ); /*not
defined, not copyable*/
};
template <typename stream_t>
class wrapper_template : public wrapper_base
{
public:
wrapper_template(stream_t* stream_) : wrapper_base
(stream_)
{ JJM_STATIC_ASSERT(sizeof(wrapper_base) == sizeof
(wrapper_template<stream_t>)); }
virtual ~wrapper_template() {}
static_cast<stream_t*>(stream)->read(s, n); }
virtual void clear(unsigned char new_state = 0)
{ static_cast<stream_t*>(stream)->clear(new_state); }
virtual unsigned char rdstate() const { return
static_cast<stream_t*>(stream)->rdstate(); }
virtual void cloneInPlace(void* ptr) const { new (ptr)
wrapper_template<stream_t>(static_cast<stream_t*>(stream)); }
private:
wrapper_template(wrapper_template const& ); /*not defined,
not copyable*/
wrapper_template& operator= (wrapper_template const& ); /
*not defined, not copyable*/
};
template <typename stream_t>
class wrapper_owning_template : public
wrapper_template<stream_t>
{
public:
wrapper_owning_template(stream_t* stream) :
wrapper_template<stream_t>(stream)
{ JJM_STATIC_ASSERT(sizeof(wrapper_base) == sizeof
(wrapper_owning_template<stream_t>)); }
virtual ~wrapper_owning_template()
{ delete static_cast<stream_t*>(this->stream); }
virtual void cloneInPlace(void* ptr) const
{ new (ptr) wrapper_owning_template<stream_t>
(static_cast<stream_t*>(this->stream)); }
};
#undef JJM_STATIC_ASSERT
public:
/*This ctor wraps the argument stream, making it usable
through a generic type istream_wrapper, aka runtime
polymorphism.
It does not take ownership over its argument stream.*/
template <typename stream_t>
istream_wrapper(stream_t& stream)
{ /*Yes. I know this is hack-ish and probably not
standard.
I'm attempting to improve locality by keeping
contained class
inside this object's memory as opposed to in a
separate piece
of separately allocated memory. Asserting that the
sizes make
sense in wrapper_template's ctor.*/
new (impl) wrapper_template<stream_t>(&stream);
}
/*This ctor wraps the argument stream, making it usable
through a generic type istream_wrapper, aka runtime
polymorphism.*/
struct take_ownership {};
template <typename stream_t>
istream_wrapper(stream_t* stream, take_ownership /*fake
argument to pick constructor*/)
{ /*Yes. I know this is hack-ish and probably not
standard.
I'm attempting to improve locality by keeping
contained class
inside this object's memory as opposed to in a
separate piece
of separately allocated memory. Asserting that the
sizes make
sense in wrapper_owning_template's ctor.*/
new (impl) wrapper_owning_template<stream_t>(&stream);
}
/*This is not equivalent to the template constructor.
Instead, it merely copies the underlying stream pointer,
making it
an equivalent copy.*/
istream_wrapper(istream_wrapper const& x) { x.getimpl()-
istream_wrapper& operator= (istream_wrapper x) { swap(x);
return *this; }
void swap(istream_wrapper& x) { getimpl()->swap(*x.getimpl
()); }
~istream_wrapper() { getimpl()->~wrapper_base(); }
/*will set eofbit if no characters left in stream
will set failbit if no characters left in stream
on EOF will return EOF*/
int get() { return getimpl()->get(); }
/*will set eofbit if no characters left in stream
will set failbit if no characters read into s
will not set failbit if some characters are read into s
will return number of characters read into s*/
size_t read(char* s, size_t const n) { return getimpl()->read
(s, n); }
/*more accurately: this sets the stream's state*/
void clear(unsigned char new_state = 0) { getimpl()->clear
(new_state); }
/*more accurately: this gets the stream's state*/
unsigned char rdstate() const { return getimpl()->rdstate(); }
JJM_ISTREAM_COMMON_FUNCTIONS
private:
char impl[sizeof(wrapper_base)];
wrapper_base* getimpl() { return
reinterpret_cast<wrapper_base*>(impl); }
wrapper_base const* getimpl() const { return
reinterpret_cast<wrapper_base const*>(impl); }
};
void swap(istream_wrapper& x, istream_wrapper& y) { x.swap(y); }
#ifdef WIN32
#pragma warning(push)
#pragma warning(disable : 4996)
#endif
class ifstream
{
public:
ifstream() : file(0), state(0) {}
ifstream(char const* filename, const char* mode = "r") : file
(0), state(0) { open(filename, mode); }
~ifstream() { if (is_open()) close(); }
bool is_open() const { return 0 != file; }
void open(char const* filename, const char* mode = "r")
{ if (is_open())
setstate(failbit);
if (!*this)
return;
file = fopen(filename, mode);
if (!file)
setstate(failbit);
}
void close()
{ if (!is_open())
setstate(failbit);
if (!fclose(file))
setstate(badbit);
file = 0;
}
int get()
{ char c = EOF;
read(&c, 1);
return c;
}
size_t read(char* s, size_t n)
{ if (!is_open() || !*this)
{ setstate(failbit);
return 0;
}
if (0 == n)
return 0;
size_t const bytes_read = fread(s, 1, n, file);
if (bytes_read != n)
{ if (feof(file))
setstate(eofbit | failbit);
else
setstate(badbit);
}
if (0 == bytes_read)
setstate(failbit);
return bytes_read;
}
void clear(unsigned char new_state = 0) { state = new_state; }
unsigned char rdstate() const { return state; }
JJM_ISTREAM_COMMON_FUNCTIONS
private:
FILE* file;
unsigned char state;
private:
ifstream(ifstream const& ); /*not defined, not copyable*/
ifstream& operator= (ifstream const& ); /*not defined, not
copyable*/
};
#ifdef WIN32
#pragma warning(pop)
#endif
template <typename stream_t, size_t buf_size = 1024>
class buffered_istream
{
public:
buffered_istream(stream_t& stream_)
: stream(stream_), start(buf), end(buf), state(0) {}
int get()
{ if (!*this)
{ setstate(failbit);
return EOF;
}
if (start == end)
{ size_t const x = stream.read(buf, buf_size);
setstate(stream.rdstate() & badbit);
if (bad())
return EOF;
if (x == 0)
setstate(eofbit);
start = buf;
end = buf + x;
}
if (eof())
{ setstate(failbit);
return EOF;
}
char const c = *start;
++start;
return c;
}
size_t read(char* s, size_t const n)
{ if (!*this)
{ setstate(failbit);
return 0;
}
if (0 == n)
return 0;
size_t bytes_read_into_user_buf = 0;
for (char const* caller_buf_end = s + n; good() && s !
= caller_buf_end; )
{ if (start == end)
{ size_t const x = stream.read(buf, buf_size);
setstate(stream.rdstate() & badbit);
if (bad())
break;
if (x == 0)
setstate(eofbit);
start = buf;
end = buf + x;
}
size_t const y = std::min(end - start,
caller_buf_end - s);
memcpy(s, buf, y);
s += y;
start += y;
bytes_read_into_user_buf += y;
}
if (0 == bytes_read_into_user_buf)
setstate(failbit);
return bytes_read_into_user_buf;
}
void clear(unsigned char new_state = 0) { state = new_state; }
unsigned char rdstate() const { return state; }
JJM_ISTREAM_COMMON_FUNCTIONS
private:
stream_t& stream;
char buf[buf_size];
char* start;
char* end;
unsigned char state;
private:
buffered_istream(buffered_istream const& ); /*not defined, not
copyable*/
buffered_istream& operator= (buffered_istream const& ); /*not
defined, not copyable*/
};
template <typename stream_t>
class formatter_istream
{
public:
formatter_istream(stream_t& stream_) : stream(stream_), state
(0), next_char(EOF) {}
int get()
{ if (!*this)
{ setstate(failbit);
return EOF;
}
if (EOF == next_char)
next_char = stream.get();
if (!stream)
{ setstate(stream.rdstate());
return EOF;
}
int x = next_char;
next_char = stream.get();
if ('\r' == x && '\n' == next_char)
{ next_char = EOF;
return '\n';
}
if ('\r' == x)
return '\n';
return x;
}
size_t read(char* s, size_t const n)
{ if (0 == n)
return 0;
size_t bytes_read_into_s = 0;
for ( ; bytes_read_into_s < n; )
{ int x = get();
if (!*this)
break;
*s = static_cast<char>(x);
++s;
++bytes_read_into_s;
}
if (0 == bytes_read_into_s)
setstate(failbit);
return bytes_read_into_s;
}
void clear(unsigned char new_state = 0) { state = new_state; }
unsigned char rdstate() const { return state; }
JJM_ISTREAM_COMMON_FUNCTIONS
private:
stream_t& stream;
unsigned char state;
int next_char;
private:
formatter_istream(formatter_istream const& ); /*not defined,
not copyable*/
formatter_istream& operator= (formatter_istream const& ); /
*not defined, not copyable*/
};
template <typename stream_t>
stream_t& getline(stream_t& stream, std::string& str, char const
delim = '\n')
{ str.clear();
for (;
{ int const c = stream.get();
if (((failbit | eofbit) == stream.rdstate()) && str.size
())
{ stream.clear(eofbit);
return stream;
}
if (!stream)
return stream;
if (c == delim)
return stream;
str.push_back(static_cast<char>(c));
}
}
/*meant to mimic std::ifstream not-binary-mode*/
class ifbfstream
: private ifstream,
private buffered_istream<ifstream>,
private formatter_istream<buffered_istream<ifstream> >
{
public:
#ifdef WIN32
#pragma warning(push)
#pragma warning(disable : 4355)
#endif
ifbfstream()
: buffered_istream<ifstream>(static_cast<ifstream&>
(*this)),
formatter_istream<buffered_istream<ifstream> >
(static_cast<buffered_istream<ifstream>&>(*this))
{}
ifbfstream(char const* filename, const char* mode = "r")
: ifstream(filename, mode),
buffered_istream<ifstream>(static_cast<ifstream&>
(*this)),
formatter_istream<buffered_istream<ifstream> >
(static_cast<buffered_istream<ifstream>&>(*this))
{}
#ifdef WIN32
#pragma warning(pop)
#endif
using ifstream::is_open;
using ifstream:pen;
using ifstream::close;
using formatter_istream<buffered_istream<ifstream> >::get;
using formatter_istream<buffered_istream<ifstream> >::read;
using formatter_istream<buffered_istream<ifstream> >::clear;
using formatter_istream<buffered_istream<ifstream> >::rdstate;
JJM_ISTREAM_COMMON_FUNCTIONS
private:
ifbfstream(ifbfstream const& ); /*not defined, not copyable*/
ifbfstream& operator= (ifbfstream const& ); /*not defined, not
copyable*/
};
#undef JJM_ISTREAM_COMMON_FUNCTIONS
}
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
template <typename stream_t>
void print_compiletime_polymorphism(stream_t& stream)
{ std::cout << "---- " << __FILE__ << " " << __LINE__ << std::endl;
for (std::string line; getline(stream, line); )
std::cout << "X" << line << std::endl;
}
void print_runtime_polymorphism(jjm::istream_wrapper stream)
{ std::cout << "---- " << __FILE__ << " " << __LINE__ << std::endl;
for (std::string line; getline(stream, line); )
std::cout << "X" << line << std::endl;
}
int main()
{
{
std:fstream fout("foo.txt");
fout << "a\n\nb\n";
}
/*Each of this should print out the same thing.
/Just a test to make sure it's all working right.*/
{ jjm::ifstream fin("foo.txt");
print_compiletime_polymorphism(fin);
}
{ jjm::ifstream fin("foo.txt");
print_runtime_polymorphism(fin);
}
{ jjm::ifstream fin("foo.txt");
jjm::buffered_istream<jjm::ifstream> buffered_fin(fin);
print_compiletime_polymorphism(buffered_fin);
}
{ jjm::ifstream fin("foo.txt");
jjm::buffered_istream<jjm::ifstream> buffered_fin(fin);
print_runtime_polymorphism(buffered_fin);
}
{ jjm::ifstream fin("foo.txt");
jjm::buffered_istream<jjm::ifstream> buffered_fin(fin);
jjm::buffered_istream<jjm::buffered_istream<jjm::ifstream> >
buffered_fin_2(buffered_fin);
print_compiletime_polymorphism(buffered_fin_2);
}
{ jjm::ifstream fin("foo.txt");
jjm::buffered_istream<jjm::ifstream> buffered_fin(fin);
jjm::buffered_istream<jjm::buffered_istream<jjm::ifstream> >
buffered_fin_2(buffered_fin);
print_runtime_polymorphism(buffered_fin_2);
}
{ jjm::ifstream fin("foo.txt");
jjm::istream_wrapper opaque_istream(fin);
jjm::buffered_istream<jjm::istream_wrapper> buffered_fin
(opaque_istream);
print_compiletime_polymorphism(buffered_fin);
}
{ jjm::ifstream fin("foo.txt");
jjm::istream_wrapper opaque_istream(fin);
jjm::buffered_istream<jjm::istream_wrapper> buffered_fin
(opaque_istream);
print_runtime_polymorphism(buffered_fin);
}
{ jjm::ifstream fin("foo.txt");
jjm::buffered_istream<jjm::ifstream> buffered_fin(fin);
jjm::formatter_istream<jjm::buffered_istream<jjm::ifstream> >
formatting_buffered_fin(buffered_fin);
print_compiletime_polymorphism(formatting_buffered_fin);
}
{ jjm::ifstream fin("foo.txt");
jjm::buffered_istream<jjm::ifstream> buffered_fin(fin);
jjm::formatter_istream<jjm::buffered_istream<jjm::ifstream> >
formatting_buffered_fin(buffered_fin);
print_runtime_polymorphism(formatting_buffered_fin);
}
{ jjm::ifbfstream fin("foo.txt");
print_compiletime_polymorphism(fin);
}
{ jjm::ifbfstream fin("foo.txt");
print_runtime_polymorphism(fin);
}
std::cout << "----" << std::endl;
}
not well documented, and most non-standard uses to be far out of the
reach of novice C++ programmers, and dare I say most competent C++
programmers. (When's the last time you've done anything with facets,
locales, etc.?)
I've never been a fan of parallel duplicate class hierarchies. It's a
huge design smell to me. The C++ standard streams have this design
smell. They have ifstream and the associated file streambuf,
stringstream its associated string streambuf, etc. The design smell
tends to indicate duplicate code and an overly complex set of classes.
If each class appears as a pair, why have two separate hierarchies?
Also, I've always liked C++ templates as compile time polymorphism. I
would think it natural to do something like create a stream which
writes to a file, then put a buffering wrapper over that, then put a
formatter over it to change '\n' to the system's native line ending,
then put a formatter over that whose constructor takes an encoding
(eg: UTF 8, ASCII, etc.) and whose operator<< functions take your
unicode string and converts it the encoding passed in the constructor.
The current std streams allow you to do this (sort of), but it's much
more complicated than what it needs to be, and it's done with runtime
polymorphism, not compile-time polymorphism of templates, so
potentially much slower.
Also, the std streams internationalization support is at best
pisspoor. The existence of locales and their meanings are
implementation defined. One cannot rely upon any of the C++ standard
locale + facet stuff for a portable program. It's also entirely
convoluted and complex, and doesn't support simple things like
changing from one encoding to another. Now, in the standard
committee's defense, internationalization is hard (tm). However, I
wish they did not try at all rather than clutter up a good standard
library with nearly useless features like locales and facets. Also,
seriously, wchar_t's size is implementation defined? Why even bother?
The new fixed size types for UTF 8, UTF 16, and UTF32 is a step in the
right direction, but from what little I understand it's still coming
up quite short. (Ex: std::string still will not support variable width
encodings like UTF 8 and UTF 16, but it will bear a name of UTF 8 and
UTF 16, a huge misnomer and disservice.)
As a start, I would suggest the following code. Now, I admit that I'm
not the most familiar with C++ std streams, but I think I know enough
to make the critics in this post. I'm just wondering if something like
the below code could not support something which the current C++ std
streams do support? And any other comments on the viability and
usability of the interface and implementation below?
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string>
namespace jjm
{
#ifdef WIN32
#pragma warning (disable : 4800)
#endif
/*
Three flags, eofbit, failbit, badbit.
eofbit is set whenever end of file (or stream, std::string, etc)
is encountered.
failbit is set whenever the operation could not complete
successfully:
ex: failure to open a file
ex: getline called and no characters left in the stream
ex: not set when getline is called with characters left in the
stream,
even if the deliminating character is not found
badbit is set when some underlying system call failed for an
"uncommon",
"unexpected", "not normal course of operations" reason:
ex: Not because of end of file, nor file doesn't exist, etc.
Some streams are wrappers over other streams. When a stream X has
been
wrapped by a stream Y, do not use stream X again for reading or
writing,
as stream Y may have read ahead from X and cached it, or Y may be
caching
data to write to X and not written it yet.
*/
/*DO NOT USE THE INTEGER VALUES DIRECTLY!
USE THE ENUM NAMES ONLY!*/
enum { eofbit = 1, failbit = 2, badbit = 4 };
/*To facilitate a safer conversion in a if / for / while / etc
condition
DO NOT USE THESE NAMES AT ALL (outside of this implemention)!*/
struct condition_conversion_class { void
condition_conversion_function() {} };
typedef void (condition_conversion_class::*condition_conversion)
();
/*each istream should have these verbatim*/
#define JJM_ISTREAM_COMMON_FUNCTIONS \
operator condition_conversion () const \
{ return (rdstate() & (failbit | badbit)) \
? 0 \
: &condition_conversion_class \
::condition_conversion_function; \
} \
bool operator! () const \
{ return (rdstate() & (failbit | badbit)) \
? true \
: false; \
} \
bool good() const { return ! rdstate(); } \
bool eof() const { return rdstate() & eofbit; } \
bool fail() const { return rdstate() & failbit; } \
bool bad() const { return rdstate() & badbit; } \
void setstate(unsigned char or_state) { clear(rdstate() |
or_state); }
class istream_wrapper
{
private:
template <bool b> struct static_assert_struct_template {};
#define JJM_STATIC_ASSERT(cond) \
typedef static_assert_struct_template<true> \
static_assert_typedef; \
typedef static_assert_struct_template<cond> \
static_assert_typedef;
class wrapper_base
{
public:
wrapper_base(void* stream_) : stream(stream_) {}
virtual ~wrapper_base() {}
virtual int get() = 0;
virtual size_t read(char* s, size_t const n) = 0;
virtual void clear(unsigned char new_state = 0) = 0;
virtual unsigned char rdstate() const = 0;
virtual void cloneInPlace(void* ptr) const = 0;
void swap(wrapper_base& x) { std::swap(stream,
x.stream); }
protected:
wrapper_base() {}
void* stream; /*declared in base class to do sizeof
(wrapper_base) later in code*/
private:
wrapper_base(wrapper_base const& ); /*not defined, not
copyable*/
wrapper_base& operator= (wrapper_base const& ); /*not
defined, not copyable*/
};
template <typename stream_t>
class wrapper_template : public wrapper_base
{
public:
wrapper_template(stream_t* stream_) : wrapper_base
(stream_)
{ JJM_STATIC_ASSERT(sizeof(wrapper_base) == sizeof
(wrapper_template<stream_t>)); }
virtual ~wrapper_template() {}
virtual size_t read(char* s, size_t const n) { returnvirtual int get() { return static_cast said:get(); }
static_cast<stream_t*>(stream)->read(s, n); }
virtual void clear(unsigned char new_state = 0)
{ static_cast<stream_t*>(stream)->clear(new_state); }
virtual unsigned char rdstate() const { return
static_cast<stream_t*>(stream)->rdstate(); }
virtual void cloneInPlace(void* ptr) const { new (ptr)
wrapper_template<stream_t>(static_cast<stream_t*>(stream)); }
private:
wrapper_template(wrapper_template const& ); /*not defined,
not copyable*/
wrapper_template& operator= (wrapper_template const& ); /
*not defined, not copyable*/
};
template <typename stream_t>
class wrapper_owning_template : public
wrapper_template<stream_t>
{
public:
wrapper_owning_template(stream_t* stream) :
wrapper_template<stream_t>(stream)
{ JJM_STATIC_ASSERT(sizeof(wrapper_base) == sizeof
(wrapper_owning_template<stream_t>)); }
virtual ~wrapper_owning_template()
{ delete static_cast<stream_t*>(this->stream); }
virtual void cloneInPlace(void* ptr) const
{ new (ptr) wrapper_owning_template<stream_t>
(static_cast<stream_t*>(this->stream)); }
};
#undef JJM_STATIC_ASSERT
public:
/*This ctor wraps the argument stream, making it usable
through a generic type istream_wrapper, aka runtime
polymorphism.
It does not take ownership over its argument stream.*/
template <typename stream_t>
istream_wrapper(stream_t& stream)
{ /*Yes. I know this is hack-ish and probably not
standard.
I'm attempting to improve locality by keeping
contained class
inside this object's memory as opposed to in a
separate piece
of separately allocated memory. Asserting that the
sizes make
sense in wrapper_template's ctor.*/
new (impl) wrapper_template<stream_t>(&stream);
}
/*This ctor wraps the argument stream, making it usable
through a generic type istream_wrapper, aka runtime
polymorphism.*/
struct take_ownership {};
template <typename stream_t>
istream_wrapper(stream_t* stream, take_ownership /*fake
argument to pick constructor*/)
{ /*Yes. I know this is hack-ish and probably not
standard.
I'm attempting to improve locality by keeping
contained class
inside this object's memory as opposed to in a
separate piece
of separately allocated memory. Asserting that the
sizes make
sense in wrapper_owning_template's ctor.*/
new (impl) wrapper_owning_template<stream_t>(&stream);
}
/*This is not equivalent to the template constructor.
Instead, it merely copies the underlying stream pointer,
making it
an equivalent copy.*/
istream_wrapper(istream_wrapper const& x) { x.getimpl()-
cloneInPlace(impl); }
istream_wrapper& operator= (istream_wrapper x) { swap(x);
return *this; }
void swap(istream_wrapper& x) { getimpl()->swap(*x.getimpl
()); }
~istream_wrapper() { getimpl()->~wrapper_base(); }
/*will set eofbit if no characters left in stream
will set failbit if no characters left in stream
on EOF will return EOF*/
int get() { return getimpl()->get(); }
/*will set eofbit if no characters left in stream
will set failbit if no characters read into s
will not set failbit if some characters are read into s
will return number of characters read into s*/
size_t read(char* s, size_t const n) { return getimpl()->read
(s, n); }
/*more accurately: this sets the stream's state*/
void clear(unsigned char new_state = 0) { getimpl()->clear
(new_state); }
/*more accurately: this gets the stream's state*/
unsigned char rdstate() const { return getimpl()->rdstate(); }
JJM_ISTREAM_COMMON_FUNCTIONS
private:
char impl[sizeof(wrapper_base)];
wrapper_base* getimpl() { return
reinterpret_cast<wrapper_base*>(impl); }
wrapper_base const* getimpl() const { return
reinterpret_cast<wrapper_base const*>(impl); }
};
void swap(istream_wrapper& x, istream_wrapper& y) { x.swap(y); }
#ifdef WIN32
#pragma warning(push)
#pragma warning(disable : 4996)
#endif
class ifstream
{
public:
ifstream() : file(0), state(0) {}
ifstream(char const* filename, const char* mode = "r") : file
(0), state(0) { open(filename, mode); }
~ifstream() { if (is_open()) close(); }
bool is_open() const { return 0 != file; }
void open(char const* filename, const char* mode = "r")
{ if (is_open())
setstate(failbit);
if (!*this)
return;
file = fopen(filename, mode);
if (!file)
setstate(failbit);
}
void close()
{ if (!is_open())
setstate(failbit);
if (!fclose(file))
setstate(badbit);
file = 0;
}
int get()
{ char c = EOF;
read(&c, 1);
return c;
}
size_t read(char* s, size_t n)
{ if (!is_open() || !*this)
{ setstate(failbit);
return 0;
}
if (0 == n)
return 0;
size_t const bytes_read = fread(s, 1, n, file);
if (bytes_read != n)
{ if (feof(file))
setstate(eofbit | failbit);
else
setstate(badbit);
}
if (0 == bytes_read)
setstate(failbit);
return bytes_read;
}
void clear(unsigned char new_state = 0) { state = new_state; }
unsigned char rdstate() const { return state; }
JJM_ISTREAM_COMMON_FUNCTIONS
private:
FILE* file;
unsigned char state;
private:
ifstream(ifstream const& ); /*not defined, not copyable*/
ifstream& operator= (ifstream const& ); /*not defined, not
copyable*/
};
#ifdef WIN32
#pragma warning(pop)
#endif
template <typename stream_t, size_t buf_size = 1024>
class buffered_istream
{
public:
buffered_istream(stream_t& stream_)
: stream(stream_), start(buf), end(buf), state(0) {}
int get()
{ if (!*this)
{ setstate(failbit);
return EOF;
}
if (start == end)
{ size_t const x = stream.read(buf, buf_size);
setstate(stream.rdstate() & badbit);
if (bad())
return EOF;
if (x == 0)
setstate(eofbit);
start = buf;
end = buf + x;
}
if (eof())
{ setstate(failbit);
return EOF;
}
char const c = *start;
++start;
return c;
}
size_t read(char* s, size_t const n)
{ if (!*this)
{ setstate(failbit);
return 0;
}
if (0 == n)
return 0;
size_t bytes_read_into_user_buf = 0;
for (char const* caller_buf_end = s + n; good() && s !
= caller_buf_end; )
{ if (start == end)
{ size_t const x = stream.read(buf, buf_size);
setstate(stream.rdstate() & badbit);
if (bad())
break;
if (x == 0)
setstate(eofbit);
start = buf;
end = buf + x;
}
size_t const y = std::min(end - start,
caller_buf_end - s);
memcpy(s, buf, y);
s += y;
start += y;
bytes_read_into_user_buf += y;
}
if (0 == bytes_read_into_user_buf)
setstate(failbit);
return bytes_read_into_user_buf;
}
void clear(unsigned char new_state = 0) { state = new_state; }
unsigned char rdstate() const { return state; }
JJM_ISTREAM_COMMON_FUNCTIONS
private:
stream_t& stream;
char buf[buf_size];
char* start;
char* end;
unsigned char state;
private:
buffered_istream(buffered_istream const& ); /*not defined, not
copyable*/
buffered_istream& operator= (buffered_istream const& ); /*not
defined, not copyable*/
};
template <typename stream_t>
class formatter_istream
{
public:
formatter_istream(stream_t& stream_) : stream(stream_), state
(0), next_char(EOF) {}
int get()
{ if (!*this)
{ setstate(failbit);
return EOF;
}
if (EOF == next_char)
next_char = stream.get();
if (!stream)
{ setstate(stream.rdstate());
return EOF;
}
int x = next_char;
next_char = stream.get();
if ('\r' == x && '\n' == next_char)
{ next_char = EOF;
return '\n';
}
if ('\r' == x)
return '\n';
return x;
}
size_t read(char* s, size_t const n)
{ if (0 == n)
return 0;
size_t bytes_read_into_s = 0;
for ( ; bytes_read_into_s < n; )
{ int x = get();
if (!*this)
break;
*s = static_cast<char>(x);
++s;
++bytes_read_into_s;
}
if (0 == bytes_read_into_s)
setstate(failbit);
return bytes_read_into_s;
}
void clear(unsigned char new_state = 0) { state = new_state; }
unsigned char rdstate() const { return state; }
JJM_ISTREAM_COMMON_FUNCTIONS
private:
stream_t& stream;
unsigned char state;
int next_char;
private:
formatter_istream(formatter_istream const& ); /*not defined,
not copyable*/
formatter_istream& operator= (formatter_istream const& ); /
*not defined, not copyable*/
};
template <typename stream_t>
stream_t& getline(stream_t& stream, std::string& str, char const
delim = '\n')
{ str.clear();
for (;
{ int const c = stream.get();
if (((failbit | eofbit) == stream.rdstate()) && str.size
())
{ stream.clear(eofbit);
return stream;
}
if (!stream)
return stream;
if (c == delim)
return stream;
str.push_back(static_cast<char>(c));
}
}
/*meant to mimic std::ifstream not-binary-mode*/
class ifbfstream
: private ifstream,
private buffered_istream<ifstream>,
private formatter_istream<buffered_istream<ifstream> >
{
public:
#ifdef WIN32
#pragma warning(push)
#pragma warning(disable : 4355)
#endif
ifbfstream()
: buffered_istream<ifstream>(static_cast<ifstream&>
(*this)),
formatter_istream<buffered_istream<ifstream> >
(static_cast<buffered_istream<ifstream>&>(*this))
{}
ifbfstream(char const* filename, const char* mode = "r")
: ifstream(filename, mode),
buffered_istream<ifstream>(static_cast<ifstream&>
(*this)),
formatter_istream<buffered_istream<ifstream> >
(static_cast<buffered_istream<ifstream>&>(*this))
{}
#ifdef WIN32
#pragma warning(pop)
#endif
using ifstream::is_open;
using ifstream:pen;
using ifstream::close;
using formatter_istream<buffered_istream<ifstream> >::get;
using formatter_istream<buffered_istream<ifstream> >::read;
using formatter_istream<buffered_istream<ifstream> >::clear;
using formatter_istream<buffered_istream<ifstream> >::rdstate;
JJM_ISTREAM_COMMON_FUNCTIONS
private:
ifbfstream(ifbfstream const& ); /*not defined, not copyable*/
ifbfstream& operator= (ifbfstream const& ); /*not defined, not
copyable*/
};
#undef JJM_ISTREAM_COMMON_FUNCTIONS
}
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
template <typename stream_t>
void print_compiletime_polymorphism(stream_t& stream)
{ std::cout << "---- " << __FILE__ << " " << __LINE__ << std::endl;
for (std::string line; getline(stream, line); )
std::cout << "X" << line << std::endl;
}
void print_runtime_polymorphism(jjm::istream_wrapper stream)
{ std::cout << "---- " << __FILE__ << " " << __LINE__ << std::endl;
for (std::string line; getline(stream, line); )
std::cout << "X" << line << std::endl;
}
int main()
{
{
std:fstream fout("foo.txt");
fout << "a\n\nb\n";
}
/*Each of this should print out the same thing.
/Just a test to make sure it's all working right.*/
{ jjm::ifstream fin("foo.txt");
print_compiletime_polymorphism(fin);
}
{ jjm::ifstream fin("foo.txt");
print_runtime_polymorphism(fin);
}
{ jjm::ifstream fin("foo.txt");
jjm::buffered_istream<jjm::ifstream> buffered_fin(fin);
print_compiletime_polymorphism(buffered_fin);
}
{ jjm::ifstream fin("foo.txt");
jjm::buffered_istream<jjm::ifstream> buffered_fin(fin);
print_runtime_polymorphism(buffered_fin);
}
{ jjm::ifstream fin("foo.txt");
jjm::buffered_istream<jjm::ifstream> buffered_fin(fin);
jjm::buffered_istream<jjm::buffered_istream<jjm::ifstream> >
buffered_fin_2(buffered_fin);
print_compiletime_polymorphism(buffered_fin_2);
}
{ jjm::ifstream fin("foo.txt");
jjm::buffered_istream<jjm::ifstream> buffered_fin(fin);
jjm::buffered_istream<jjm::buffered_istream<jjm::ifstream> >
buffered_fin_2(buffered_fin);
print_runtime_polymorphism(buffered_fin_2);
}
{ jjm::ifstream fin("foo.txt");
jjm::istream_wrapper opaque_istream(fin);
jjm::buffered_istream<jjm::istream_wrapper> buffered_fin
(opaque_istream);
print_compiletime_polymorphism(buffered_fin);
}
{ jjm::ifstream fin("foo.txt");
jjm::istream_wrapper opaque_istream(fin);
jjm::buffered_istream<jjm::istream_wrapper> buffered_fin
(opaque_istream);
print_runtime_polymorphism(buffered_fin);
}
{ jjm::ifstream fin("foo.txt");
jjm::buffered_istream<jjm::ifstream> buffered_fin(fin);
jjm::formatter_istream<jjm::buffered_istream<jjm::ifstream> >
formatting_buffered_fin(buffered_fin);
print_compiletime_polymorphism(formatting_buffered_fin);
}
{ jjm::ifstream fin("foo.txt");
jjm::buffered_istream<jjm::ifstream> buffered_fin(fin);
jjm::formatter_istream<jjm::buffered_istream<jjm::ifstream> >
formatting_buffered_fin(buffered_fin);
print_runtime_polymorphism(formatting_buffered_fin);
}
{ jjm::ifbfstream fin("foo.txt");
print_compiletime_polymorphism(fin);
}
{ jjm::ifbfstream fin("foo.txt");
print_runtime_polymorphism(fin);
}
std::cout << "----" << std::endl;
}