Re: concrete example on "inheriting" from ostream
AlfC wrote:
Formaters and/or custom manipulators are good and I had been using them
before; that may do part of the job.
However I was looking for a more drastic format change associated
explicitly with the kind of output sink (let's stop calling output
stream to not confuse with it with the std::ostream classes)
Logically, this could be done very well with a sticky format.
The real problem, I think, is that you have to change the format
for some standard types in ways the standard didn't consider.
OK, I will be specific with the objective: I wanted to output many
types to a LaTeX source file (sorry for those not familiar with LaTeX).
Some of these types are mathematical structures that need something
better than a dumb terminal for output and specially debugging.
(although I still wanted to keep and define some quick printing
routines in case the only available output is a terminal)
so I wanted something that worked like this:
latex::ofstream lout("file.tex");
lout<<int(9)<<" hello "<<std::complex<double>(2,3)<<"
"<<custom_class_with_mathematical_structure()<<std::endl;
file.tex will then contain a latex document source, following the
proper latex format so express this numbers and character strings.
Logically, the correct way to do this would be with some sort of
sticky formatting state. (This would also allow embedding LaTeX
in some other document, or vice versa.) In the case of LaTeX,
at least, I think the only problem would be modifying the
formatting of complex; I don't think that there are any other
pre-defined formats in the standard which would cause problems.
Another way of looking at the problem is that this
custom_classes will be smart and know that when they are
printed to a "latex sink" they can express them selves in a
full mathematical-structured way, while for simple types the
changes in the format are just slight or absent. At the same
time this custom_classes will output to simplified text
versions if the sink is a std::ostream (such as std::cout).
Some solutions I tried:
1) Inheriting from ostream caused problems because operator<< are not
virtual in std::ostream and cause ambiguity if I want to customize it.
[So I decided to put std::ostream as a member of latex::ostream (sorry
for duplicated names)]
2) automatizing the compile time generation of template<typename T>
operator<<(T t) to keep the default behavior of std::ostream didn't
work because then any friend operator<<(latex::ostream&, type const&)
[which I wanted to define inside custom classes] then will conflict
with the template member function mentioned.
Typically, when you are wrapping an iostream, the operator<<
functions are member templates. Which shouldn't prevent you
from providing custom versions; all other things being equal,
overload resolution should prefer the non-template version (or
you can explicitly specialize the template function for the same
effect).
currently the design is the following code. The only problem right now
is that I can't control the initialization order of the base class
ostream and member std::ofstream in the class ofstream.
You don't want the two members. Just an ostream is enough.
Your ofstream should contain an std::filebuf, just like
std::ofstream does, and not an ofstream. And yes, you probably
need to derive, rather than to simply contain, in order to
ensure correct order of initialization.
(I am very sorry by the similar names) This initialization
order gives a warning in gcc-4 which I would like to get rid
off. I somewhere read that private virtual inheritance
sometimes is a solution to force initialization order of base
relative to members but I didn't manage to make it work.
Here is the code:
/** code **/ /** attention to the namespace qualifier, all this is
meant to be inside namespace latex**/
I'd definitly change the names. You're bound to encounter users
you do a "using namespace std;", and then all sorts of confusion
will result.
class ostream{
std::ostream os_;
public:
ostream(std::streambuf* buf) : os_(buf), math_count_(0) {}
ostream(std::ostream& os) : os_(os.rdbuf()), math_count_(0) {}
virtual ~ostream(){}
ostream& operator<<(int i ){os_<<i ; return *this;}
ostream& operator<<(char c ){os_<<c ; return *this;}
ostream& operator<<(char* const cs){os_<<cs; return *this;}
ostream& operator<<(double d ){os_<<d<<'.'; return *this;}
ostream& operator<<(std::complex<double> const& c); //defined
The way I usually do this is to have a public function
getStream(), which returns os_, and to use global template
functions:
template< typename T >
latex::ostream&
operator<<( latex::ostream& dest, T const& obj )
{
dest.getStream() << obj ;
return dest ;
}
// This is necessary because the manipulators are
// templates, so the compiler can't do type deduction
// if they are used as an argument to a template function.
latex::ostream&
operator<<( latex::ostream& dest,
std::ostream& (*manip)( std::ostream& ) )
{
dest.getStream() << manip ;
return dest ;
}
// I've needed this as well for some older compilers,
// although it isn't necessary for g++ 4.1.0...
latex::ostream&
operator<<( latex::ostream& dest,
char const* str )
{
dest.getStream() << str ;
return dest ;
}
// any other types for which the basic template isn't
// valid...
later to output eg. 3+2i
void set_math(){math_count_++; if(math_count_==1) os_<<"$";}
//technical issue with latex
void unset_math(){math_count_--; if(math_count_==0) os_<<"$";}
private:
int math_count_;
};
class ofstream : public ostream{
std::ofstream ofs_;
This isn't what you want. You need something more along the
lines of:
struct FileBufWrapper
{
public:
FileBufWrapper( std::string const& filename,
std::ios::openmode mode )
{
myFilebuf.open( filename.c_str(), mode ) ;
}
streambuf* filebuf()
{
return &myFilebuf ;
}
private:
std::filebuf myFilebuf ;
} ;
class ofstream : private virtual FileBufWrapper,
public latex::ostream
{
public:
explicit ofstream( std::string const& filename )
: FileBufWrapper( filename, std::ios::out )
, latex::ostream( filebuf() )
{
}
} ;
public:
ofstream(std::string const& filename) :
ofs_(filename.c_str()), // <- WARNING HERE
ostream(ofs_.rdbuf()){
ofs_<<("\\documentclass[12pt]{article}")<<std::endl;
ofs_<<("\\begin{document}")<<std::endl;
}
virtual ~ofstream(){
ofs_<<("\\end{document}")<<std::endl;
}
};
/** end of code **/
Suggestions for solving the warning are welcome, other
alternatives are welcomed. Thoughts about whether the warning
can be pointing to a serious problem are also welcomed.
Well, the code definitly contains undefined behavior, and I
suspect that with some implementations of ofstream, or with some
compilers, it will core dump. The problem is that you
initialize the ostream subclass with the expression
ofs_.rdbuf(), and ofs_ has not yet been initialized
(constructed). Regardless of the order you write the
initializers, initialization occurs in an order determined by
the class definition:
-- virtual bases, in the order they were declared (left to
right), if and only if this class is the most derived, then
-- immediate bases, in the order they were declared (left to
right), then
-- members, in the order they were declared.
Since ofs_ is a member, and latex::ostream a base,
latex::ostream is initialized first. Which means that ofs_ has
not been constructed when you call its member function rdbuf();
depending on the implementation of ofstream and the way the
compiler is implemented, it might work, or it might not.
Deriving private virtual from std::ofstream should work, but you
don't really need an ofstream, just a filebuf. Whence the
simple wrapper. (Deriving private virtual from std::filebuf
doesn't work, because you have no way of getting the address of
the subclass to pass it to latex::ostream. For some strange
reason, calling a member function on the constructed base object
is legal here, but converting the this pointer to the base
object type is undefined behavior---and doesn't work with at
least one compiler I've used.)
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient?e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S?mard, 78210 St.-Cyr-l'?cole, France, +33 (0)1 30 23 00 34
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]