I created a class called SkipToChar that's supposed to be able to be used as follows:
std::ostringstream oss;
oss << "Hello," << SkipToChar(7) << "world!" << std::endl;
Which would print "Hello, world!" (notice the space.) Basically it's supposed to skip to the character at the specified index using spaces. But apparently the compiler doesn't recognize the operator<< I created for it. Interestingly, calling operator<< explicitly, even without giving any template parameters (like operator<<(oss, SkipToChar(7)); works fine; it just doesn't work if I actual
Here's my code:
#include <iostream>
#include <sstream>
template <typename _Elem>
struct basic_SkipToChar
{
typename std::basic_string<_Elem>::size_type pos;
basic_SkipToChar(typename std::basic_string<_Elem>::size_type position)
{
pos = position;
}
};
template <typename _Elem>
inline std::basic_ostringstream<_Elem> &operator<<(std::basic_ostringstream<_Elem> &oss, const basic_SkipToChar<_Elem> &skip)
{
typename std::basic_string<_Elem>::size_type length = oss.str().length();
for (typename std::basic_string<_Elem>::size_type i = length; i < skip.pos; i++) {
oss << (_Elem)' ';
}
return oss;
}
typedef basic_SkipToChar<char> SkipToChar;
typedef basic_SkipToChar<wchar_t> WSkipToChar;
int main(int argc, char *argv[])
{
std::ostringstream oss;
/*ERROR*/ oss << "Hello," << SkipToChar(8) << "world!" << std::endl;
std::cout << oss.str();
return 0;
}
It gives me the following error when I try to compile it:
error C2679: binary '<<' : no operator found which takes a right-hand operand of type 'basic_SkipToChar<char>' (or there is no acceptable conversion)
I marked the line the error is on with a comment. What's going wrong here?
oss << "Hello," returns std::basic_ostream<char> (it loses the string part).
So your method doesn't match (expect std::basic_ostringstream but got std::basic_ostream<char>).
As Jarod42 points out, the return type of all of the built-in
operator<< is std::ostream&. This is a basic principle of
how streams operator; except for the actual sink or source, the
stream should be indifferent as to where the data is going to or
coming from.
It's possible to provide manipulators which only work for one
type of stream, or do different things for different types of
streams, by using a dynamic_cast in the manipulator:
std::ostream&
operator<<( std::ostream& dest, SkipToChar const& manip )
{
std::ostringstream* d = dynamic_cast<std::ostringstream*>( &dest );
if ( d != nullptr ) {
// ...
}
return dest;
}
In general, however, this is a very bad idea; clients will be
very surprised that outputting to a string results in different
text than outputting to a file.
What you seem to be trying to do is to more or less emulate
a form of tabbing. The best way to do this is to use
a filtering output streambuf, which keeps track of where you are
in the line, and can be inserted between the std::ostream and
the actual sink regardless of the type of stream:
class TabbingOutputStreambuf : public std::streambuf
{
std::streambuf* myDest;
std::ostream* myOwner;
int myInLineCount;
public:
TabbingOutputStreambuf( std::streambuf* dest )
: myDest( dest )
, myOwner( nullptr )
, myInLineCount( 0 )
{
}
TabbingOutputStreambuf( std::ostream& dest )
: myDest( dest.rdbuf() )
, myOwner( &dest )
, myInLineCount( 0 )
{
myOwner.rdbuf( this );
}
~TabbingOutputStreambuf()
{
if ( myOwner != nullptr ) {
myOwner->rdbuf( myDest );
}
}
int overflow( int ch ) override
{
if ( ch == '\n' ) {
myInLineCount = 0;
} else {
++ myInLineCount;
}
return myDest->sputc( ch );
}
// Special function...
int tabTo( int n )
{
int retval = 0;
while ( retval == 0 && myInLineCount < n ) {
if ( overflow( ' ' ) == EOF ) {
retval = EOF;
}
}
return retval;
}
};
Your manipulator would then be:
std::ostream&
operator<<( std::ostream& dest, SkipToChar const& manip )
{
TabbingOutputStreambuf* sb = dynamic_cast<TabbingOutputStreambuf*>( dest.rdbuf() );
assert( sb != nullptr );
try {
if ( sb->tabTo( manip.pos ) == EOF ) {
dest.setstate( std::badbit );
}
} catch ( ... ) {
dest.setstate( std::badbit );
}
return dest;
}
This is still not ideal, since it will fail with an unprotected
buffer, but you'd only use the manipulator in a context like:
void
generateOutput( std::ostream& dest )
{
TabbingOutputStreambuf tabber( dest );
dest << "Hello, " << SkipToChar( 7 ) << "world!";
// ...
}
And this will work regardless of the type of stream passed to
the function (including custom ostream classes that you don't
even know about).
EDIT:
One last point: don't bother with making things templates until you've got the basic version working. (For what it's worth, your code doesn't work correctly for wchar_t streams anyway. To output a space in them, you need to get the embedded locale, get the ctype facet from it, and use its widen member function.)
Related
Same question as Create a streambuf from const char* except that I can't use boost.
I have to implement an function that takes a const char * as input parameter, and to do so I have to call an other function that takes a istream as input parameter.
Here is a sample of code very simplified:
#include <iostream>
#include <string>
using namespace std ;
void inner_function_I_cant_change ( istream & input ) // the function I must use
{
string s ; // dummy implementation
input >> s ;
cout << s.size() << " : <" << s << ">" << endl ;
}
struct externbuf : streambuf // my own streambuf using a char*...
{
int size ;
bool done ;
char * buffer ;
externbuf ( const char * buffer , int size ) :
size(size),
done(false),
buffer(const_cast<char*>( buffer )) {} // ...that forces me to an ugly const_cast !
int underflow ()
{
if (this->gptr() == this->egptr())
{
if (done) return std::char_traits<char>::eof() ;
this->setg( buffer,buffer,buffer+size ) ;
done = true ;
}
return char_traits<char>::to_int_type( *this->gptr()) ;
}
};
void API_function_I_must_povide ( const char * data , int size ) // the function I must implement
{
externbuf buf( data,size ) ;
istream input( &buf ) ;
inner_function_I_cant_change( input ) ;
}
int main ()
{
API_function_I_must_povide( "bazinga!",8 ) ;
}
This code I works well but I had to do an ugly const_cast !
I tried using a basic_streambuf<const char, char_traits<const char> > instead of a streambuf but I get many errors that I didn't understand well.
Is there a proper way to do it ?
(and, as I said, I can't use boost)
Thanks !
Thanks Remy, your link to Art Of Code made my day!
So, for those who are interested, here my new code, without memcpy and ugly const_cast:
#include <iostream>
#include <string>
using namespace std ;
void inner_function_I_cant_change ( istream & input )
{
char buffer [1000] ;
input.read( buffer,sizeof buffer ) ;
string s1( buffer,input.gcount()) ;
string s2( "hello \0 world !",15 ) ;
if (s1 == s2)
cout << "success!" ;
}
struct externbuf : public streambuf
{
externbuf ( const char * data , unsigned int len ) : begin(data),crt(data),end(data + len) {}
int_type underflow ()
{
return crt == end ? traits_type::eof() : traits_type::to_int_type( *crt ) ;
}
int_type uflow ()
{
return crt == end ? traits_type::eof() : traits_type::to_int_type( *crt++ ) ;
}
int_type pbackfail ( int_type ch )
{
bool cond = crt == begin || (ch != traits_type::eof() && ch != crt[-1]) ;
return cond ? traits_type::eof() : traits_type::to_int_type( *--crt ) ;
}
streamsize showmanyc ()
{
return end - crt ;
}
const char *begin,*crt,*end ;
};
void API_function_I_must_povide ( const char * data , int size )
{
externbuf buf( data,size ) ;
istream input( &buf ) ;
inner_function_I_cant_change( input ) ;
}
int main ()
{
API_function_I_must_povide( "hello \0 world !",15 ) ;
}
Thanks Pete, your solution is development-less but I'm afraid it induces a memcpy from data to the inner buffer of the std::string. And since my buffer may be very big, I try to avoid them as much as possible.
I am having an odd problem:
metadata.h:
class metadata
{
public:
metadata ( std::string filename );
template <class T> T get ( std::string key ) { return m_data.get<T> ( key ); }
private:
boost::property_tree::ptree m_data;
};
metadata.cpp:
metadata::metadata ( std::string filename )
{
try {
boost::property_tree::read_info ( filename, m_data );
} catch ( boost::property_tree::file_parser_error err ) {
std::cerr << "[!] Unable to open "
<< filename
<< ": "
<< err.message()
<< "!"
<< std::endl;
throw except ( "Error opening metadata file." );
}
}
asset.h:
template <class T> class assetManager {
public:
void load ( std::string filename );
T get ( std::string filename );
T operator[] ( std::string filename );
void drop ( std::string filename ) { m_assets.erase ( filename ); }
void clear() { m_assets.clear(); }
private:
std::map<std::string,T> m_assets;
};
template <class T> void assetManager<T>::load ( std::string filename )
{
if ( m_assets [ filename ] == nullptr ) {
m_assets.erase ( filename );
m_assets.insert ( std::pair <std::string,T> ( filename, new T ( filename ) ) );
}
}
template <class T> T assetManager<T>::get ( std::string filename )
{
T ret = m_assets.at ( filename );
return ret;
}
For some reason, this line here:
m_metadata.load ( boost::filesystem::current_path().string() + "/engine.conf" );
I am getting the following compiler error from g++:
src/core/../core/../asset/asset.h|22|error: cannot convert ‘std::string {aka std::basic_string}’ to ‘metadata*’ in initialization
At no point, as far as I can tell, have I programmed anything even slightly resembling a conversion of a string into my metdata pointer here. Any ideas?
Inside assetManager<T>::load this subexpression
std::pair <std::string,T> ( filename, new T ( filename ) )
is obviously incorrect. new T will produce T *. Meanwhile your pair is declared with T as its second member. You cannot use T * to initialize T, unless you have a proper conversion constructor in T.
If T is metadata, then for this problem I'd actually expect an error message saying that it is impossible to convert metadata * to std::string, not the other way around. You will see the same error if you attempt this
metadata m(new metadata("test"));
Another issue is that you are creating a pair in which the first member has type std::string. However, in std::map<std::string, T>::value_type the first member of the pair is actually const std::string. The code will compile, but it will require a conversion from your std::pair<std::string, T> to map's std::pair<const std::string, T>. This might become performance issue. Which is why a much better idea would be to use std::map<std::string, T>::value_type as pair type, instead of trying to spell it out manually.
In the comments you stated that m_metadata is assetManager<metadata*>. In that case T is metadata *. That explains everything, including the error message. In that case the problem is even more simple and localized. This
new T ( filename )
alone is what causing the error. That means that new T produces a metadata * object and attempts to initialize it with std::string. Hence the error message. It is not possible to initialize metadata * with std::string because it is not possible to convert std::string to metadata *.
On top of that, such new expression will return a metadata ** value, which is definitely not what you need.
I changed m_metadata to an assetManager then made a few changes thus:
template <class T> class assetManager {
public:
void load ( std::string filename );
T get ( std::string filename );
T operator[] ( std::string filename );
void drop ( std::string filename ) { m_assets.erase ( filename ); }
void clear() { m_assets.clear(); }
private:
std::map<std::string,T*> m_assets;
};
template <class T> void assetManager<T>::load ( std::string filename )
{
if ( m_assets [ filename ] == nullptr ) {
m_assets.erase ( filename );
m_assets.insert ( std::pair <std::string,T*> ( filename, new T ( filename ) ) );
}
}
template <class T> T assetManager<T>::get ( std::string filename )
{
T ret = *m_assets.at ( filename );
return ret;
}
It compiles and runs now. Though my problem is fixed it is still a mystery to me why the compiler thinks I was trying a std::string to metadata* conversion.
This question already has answers here:
How to construct a c++ fstream from a POSIX file descriptor?
(8 answers)
Closed 10 years ago.
It is well known that there are three default I/O streams which are mapped to predefined objects in the standard library:
0: std::istream std::cin
1: std::ostream std::cout
2: std::ostream std::cerr and std::ostream std::clog
However, from within (e.g.) a bash script you can create additional streams (3,4,...).
So, can you create an extra output stream with the descriptor 3 and bind it to an std::ostream custom object? If so, how? std::ofstream doesn't do the trick as it would create a file with the name "3" which is not what I want.
Edit: It doesn't have to be portable. It suffices if it works on POSIX.
It is not possible if you need your program to be portable. The C++ 11 Standard does not specify a unified way of doing that.
However, you can define your own output stream buffer which overrides the overflow() and xsputn() virtual functions and writes each character or sequence of characters to the stream with the specified descriptor using system-specific API.
Something along these lines:
class my_ostream_buf : public std::streambuf
{
public:
my_ostream_buf(int fd) : _fd(fd) { }
protected:
virtual int_type overflow (int_type c)
{
if (c != EOF)
{
char ch = c;
if (write(_fd, &ch, 1) != 1)
{
return EOF;
}
}
return c;
}
// This is not strictly necessary, but performance is better if you
// write a sequence of characters all at once rather than writing
// each individual character through a separate system call.
virtual std::streamsize xsputn(const char* s, std::streamsize num)
{
return write(_fd, s, num);
}
private:
int _fd = 0;
};
And this is how you would use it:
using namespace std;
int main()
{
int fd = ...; // Any file descriptor
my_ostream_buf buf(fd);
ostream os(&buf); // Take care of the lifetime of `buf` here, or create your
// own class that derives from ostream and encapsulates an
// object of type my_ostream_buf
os << "Hello" << endl;
}
There's nothing provided for this in the standard. In a good
implementation of IOStream, there should be some additional,
implementation specific constructors for std::filebuf, which
take a system file descripter (whose type will depend on the
system), and create a filebuf from that. If not, you'll have to
create your own streambuf. This can be more or less difficult,
depending on what you need: if you're just need a simple,
uni-directional stream (read or write, but not both), with no
support for seeking and no code translation on input, it's
relatively simple. (But you still have to be familiar with the
system level requests, like read or write.) If you want to
support everything that filebuf does, it is significantly more
complicated.
EDIT:
Just thought I'd add an example. Since you speak of bash,
I'll suppose Unix:
class FdStreambuf : public std::streambuf
{
int myFd;
char buffer[1024];
bool writeBuffer()
{
int len = pptr() - pbase();
return len == 0 || write( myFd, pptr(), len ) == len;
}
protected:
int overflow( int ch )
{
int results = ch == traits::eof() ? 0 : ch;
if ( pbase() != NULL ) {
if ( ! writeBuffer() ) {
results = traits::eof();
}
}
setp( buffer, buffer + sizeof( buffer ) );
sputc( ch );
return ch;
}
int sync()
{
return writeBuffer() ? 0 : -1;
}
public:
FdStreambuf( int fd ) : myFd( fd ) {}
int close()
{
sync();
return ::close( myFd );
}
};
class FdOStream : private FdStreambuf, public std::ostream
{
public:
FdOStream( int fd )
: FdStreambuf( fd )
, std::ostream( this )
{
}
void close()
{
if ( FdStreambuf::close() != 0 ) {
setstate( std::ios_base::badbit );
}
}
};
(I think that's all that's necessary, but it's possible I've
forgotten something.)
I combined Andy's and James's answer and this is what I got (in case somebody needs this)
Streams.h
#pragma once
#include <ostream>
#include <unistd.h>
namespace util {
class StreamWrapperImpl : public std::ostream {
private:
typedef std::streambuf* OwnedBufPtr;
OwnedBufPtr const buf;
public:
StreamWrapperImpl(OwnedBufPtr buf)
: std::ostream(buf)
, buf(buf)
{}
virtual ~StreamWrapperImpl() {
delete buf;
}
};
template <typename Buf>
class StreamWrapper : public StreamWrapperImpl {
public:
StreamWrapper()
: StreamWrapperImpl(new Buf())
{}
template <typename Arg>
StreamWrapper(Arg arg) // this could use some perfect forwarding in C++11
: StreamWrapperImpl(new Buf(arg))
{}
};
class FdStreamBuf : public std::streambuf {
private:
int fd;
protected:
virtual int_type overflow(int_type c) {
if (c != EOF) {
char const ch = c;
if (write(fd, &ch, 1) != 1)
return EOF;
}
return c;
}
virtual std::streamsize xsputn(char const* s, std::streamsize num) {
return write(fd, s, num);
}
public:
FdStreamBuf(int fd)
: fd(fd)
{
}
};
}
struct T
{
void eat(std::string const& segment)
{
buffer << segment;
std::string sentence;
while (std::getline(buffer, sentence))
std::cout << "[" << sentence.size() << "]";
}
std::stringstream buffer;
};
int main() {
T t;
t.eat("A\r\nB\nC\nD");
// ^^ ^ ^ ^
}
// Actual output: [2][1][1][1]
// Desired output: [1][1][1][1]
I would like the std::stringstream to strip that carriage return for me (and would prefer not to have to copy and modify segment).
How might I go about this? I would have thought that this would happen anyway, on Linux, for a stream in text mode... but perhaps that mechanism is in the logic of file streams.
This is a general problem on Unix machines when reading files created on
a Windows machine. I would suggest doing the clean-up at the input
level.
One of the best solution I've found when reading line based files is to
create a class something like:
class Line
{
std::string myText;
public:
friend std::istream& operator>>( std::istream& source, Line& dest )
{
std::getline( source, dest.myText );
if ( source ) {
dest.myText.erase(
std::remove( dest.myText.begin(), dest.myText.end(), '\015' ),
dest.myText.end() );
}
return source;
}
operator std::string() const
{
return myText;
}
};
You can add other functions as necessary: the automatic type conversion
doesn't play when trying to match templates, for example, and I found it
useful to add friends to wrap boost::regex_match.
I use this (without the '\015' removal) even when I don't have to
worry about Windows/Linux differences; it supports reading lines using
std::istream_iterator<Line>, for example.
Another solution would be to use a filtering streambuf, inserted into
the input stream. This is also very simple:
class RemoveCRStreambuf : public std::streambuf
{
std::streambuf* mySource;
char myBuffer; // One char buffer required for input.
protected:
int underflow()
{
int results = mySource->sbumpc();
while ( results == '\015' ) {
results = mySource->sbumpc();
}
if ( results != EOF ) {
myBuffer = results;
setg( &myBuffer, &myBuffer + 1, &myBuffer + 1 );
}
return results;
}
public:
RemoveCRStreambuf( std::streambuf* source )
: mySource( source )
{
}
};
To insert it:
std::streambuf* originalSB = source->rdbuf();
RemoveCRStreambuf newSB( originalSB );
source->rdbuf( &newSB );
// Do input here...
source->rdbuf( originalSB ); // Restore...
(Obviously, using some sort of RAII for the restoration would be
preferable. My own filtering streambuf have a constructor which takes
an std::istream; they save a pointer to this as well, and restore the
streambuf in their destructor.)
I've already asked 2 questions kind of related to this project, and i've reached this conclusion. Writing the size of the Struct to the file , and then reading it back is the best way to do this.
I'm creating a program for a homework assignment that will allow me to maintain inventory. I need to read / write multiple structs of the same type to a file.
The problem is... this is really complicated and i'm having trouble wrapping my head around the whole process. I've seen a bunch of examples and i'm trying to put it all together. I'm getting compile errors... and I have zero clue on how to fix them. If you could help me on this I would be so appreciative... thank you. I'm so lost right now...
**** HOPEFULLY THE LAST EDIT #3 *************
My Code:
// Project 5.cpp : main project file.
#include "stdafx.h"
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>
using namespace System;
using namespace std;
#pragma hdrstop
int checkCommand (string line);
template<typename Template>
void readFromFile(Template&);
template<typename Template>
void writeToFile(Template&);
template<typename T>
void writeVector(ofstream &out, const vector<T> &vec);
template<typename Template>
void readVector(ifstream& in, vector<Template>& vec);
struct InventoryItem {
string Item;
string Description;
int Quantity;
int wholesaleCost;
int retailCost;
int dateAdded;
} ;
int main(void)
{
cout << "Welcome to the Inventory Manager extreme! [Version 1.0]" << endl;
vector<InventoryItem> structList;
ofstream out("data.dat");
writeVector( out, structList );
while (1)
{
string line = "";
cout << endl;
cout << "Commands: " << endl;
cout << "1: Add a new record " << endl;
cout << "2: Display a record " << endl;
cout << "3: Edit a current record " << endl;
cout << "4: Exit the program " << endl;
cout << endl;
cout << "Enter a command 1-4: ";
getline(cin , line);
int rValue = checkCommand(line);
if (rValue == 1)
{
cout << "You've entered a invalid command! Try Again." << endl;
} else if (rValue == 2){
cout << "Error calling command!" << endl;
} else if (!rValue) {
break;
}
}
system("pause");
return 0;
}
int checkCommand (string line)
{
int intReturn = atoi(line.c_str());
int status = 3;
switch (intReturn)
{
case 1:
break;
case 2:
break;
case 3:
break;
case 4:
status = 0;
break;
default:
status = 1;
break;
}
return status;
}
template <typename Template>
void readFromFile(Template& t)
{
ifstream in("data.dat");
readVector(in, t); Need to figure out how to pass the vector structList via a Template
in.close();
}
template <typename Template>
void writeToFile(Template& t)
{
ofstream out("data.dat");
readVector(out, t); Need to figure out how to pass the vector structList via a Template
out.close();
}
template<typename T>
void writeVector(ofstream &out, const vector<T> &vec)
{
out << vec.size();
for(vector<T>::const_iterator i = vec.begin(); i != vec.end(); ++i)
{
out << *i; // SUPER long compile error
}
}
template<typename T>
vector<T> readVector(ifstream &in)
{
size_t size;
in >> size;
vector<T> vec;
vec.reserve(size);
for(int i = 0; i < size; ++i)
{
T tmp;
in >> tmp;
vec.push_back(tmp);
}
return vec;
}
My Compile Errors:
1>.\Project 5.cpp(128) : error C2679: binary '<<' : no operator found which takes a right-hand operand of type 'const InventoryItem' (or there is no acceptable conversion)
1> C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\include\ostream(653): could be 'std::basic_ostream<_Elem,_Traits> &std::operator <<<char,std::char_traits<char>>(std::basic_ostream<_Elem,_Traits> &,const char *)'
1> with
That is the only error i'm getting now. I see your code is SO Much better. My new compiler error is SUPER long. I've shown where it the error points to. Could you help me just one last time?
Your read and write functions are buggy. In particular, you should be doing something like this instead:
template<typename T>
void write(ofstream &out, const T &t)
{
out << T;
}
OLD: bind1st requires you do include functional for it to work:
#include <functional>
Instead of dealing with all these functions and such, though, it'd be better to rely on iterators:
template<typename T>
void writeVector(ofstream &out, const vector<T> &vec)
{
out << vec.size();
for(vector<T>::const_iterator i = vec.begin(); i != vec.end(); ++i)
{
out << *i;
}
}
template<typename T>
vector<T> readVector(ifstream &in)
{
size_t size;
in >> size;
vector<T> vec;
vec.reserve(size);
for(int i = 0; i < size; ++i)
{
T tmp;
in >> tmp;
vec.push_back(tmp);
}
return vec;
}
You'd want functions to read and write your InventoryItem as well, probably:
ostream &operator<<(ostream &out, const InventoryItem &i)
{
out << i.Item << i.Description; // FIXME Read/write strings properly.
out << i.Quantity;
out << i.wholesaleCost << i.retailCost;
out << i.dateAdded;
}
istream &operator>>(istream &out, InventoryItem &i)
{
// Keep in same order as operator<<(ostream &, const InventoryItem &)!
in >> i.Item >> i.Description; // FIXME Read/write strings properly.
in >> i.Quantity;
in >> i.wholesaleCost >> i.retailCost;
in >> i.dateAdded;
}
NOTE: This is not an answer to the compilation errors you are getting, but rather a broader view of the persistence problem you are handling.
Serialization and deserialization is not the simplest problem you can work on. My advice would be investing in learning libraries (boost::serialization) and using them. They have already worked out many of the problems you will face at one time or another. Plus they already have different output formats (binary, xml, json...)
The first thing you must decide, that is if you decide to go ahead and implement your own, is what will be the file format and whether it suits all your needs. Will it always be used in the same environment? Will the platform change (32/64bits)? You can decide to make it binary as it is the simplest, or make it readable for a human being. If you decide on XML, JSON or any other more complex formats, just forget it and use a library.
The simplest solution is working on a binary file and it is also the solution that will give you a smallest file. On the other hand, it is quite sensible to architecture changes (say you migrate from a 32 to a 64 bit architecture/OS)
After deciding the format you will need to work on the extra information that is not part of your objects now but needs to be inserted into the file for later retrieval. Then start working (and testing) from the smallest parts to more complex elements.
Another advice would be to start working with the simplest most defined part and build from there on. Start avoiding templates as much as possible, and once you have it clear and working for a given data type, work on how to generalize it for any other type.
Disclaimer: I have written the code directly on the browser, so there could be some errors, typos or just about anything :)
Text
The first simple approach is just writting a textual representation of the text. The advantage is that it is portable and shorter in code (if not simpler) than the binary approach. The resulting files will be bigger but user readable.
At this point you need to know how reading text works with iostreams. Particularly, whenever you try to read a string the system will read characters until it reaches a separator. This means that the following code:
std::string str;
std::cin >> str;
will only read up to the first space, tab or end of line. When reading numbers (ints as an example) the system will read all valid digits up to the first non-valid digit. That is:
int i;
std::cin >> i;
with input 12345a will consume all characters up to 'a'. You need to know this because that will influence the way you persist data for later retrieval.
// input: "This is a long Description"
std::string str;
std::cin >> str; // Will read 'This' but ignore the rest
int a = 1;
int b = 2;
std::cout << a << b; // will produce '12'
// input: 12
int read;
std::cint >> read; // will read 12, not 1
So you pretty much need separators to insert in the output and to parse the input. For sample purposes I will select the '|' character. It must be a character that does not appear in the text fields.
It will also be a good idea to not only separate elements but also add some extra info (size of the vector). For the elements in the vector you can decide to use a different separator. If you want to be able to read the file manually you can use '\n' so that each item is in its own line
namespace textual {
std::ostream & operator<<( std::ostream& o, InventoryItem const & data )
{
return o << data.Item << "|" << data.Description << "|" << data.Quantity
<< "|" << data. ...;
}
std::ostream & operator<<( std::ostream & o, std::vector<InventoryItem> const & v )
{
o << v.size() << std::endl;
for ( int i = 0; i < v.size(); ++i ) {
o << v[i] << std::endl; // will call the above defined operator<<
}
}
}
For reading, you will need to split the input by '\n' to get each element and then with '|' to parse the InventoryItem:
namespace textual {
template <typename T>
void parse( std::string const & str, T & data )
{
std::istringstream st( str ); // Create a stream with the string
st >> data; // use operator>>( std::istream
}
std::istream & operator>>( std::istream & i, InventoryItem & data )
{
getline( i, data.Item, '|' );
getline( i, data.Description, '|' );
std::string tmp;
getline( i, tmp, '|' ); // Quantity in text
parse( tmp, data.Quantity );
getline( i, tmp, '|' ); // wholesaleCost in text
parse( tmp, data. wholesaleCost );
// ...
return i;
}
std::istream & operator>>( std::istream & i, std::vector<InventoryItem> & data )
{
int size;
std::string tmp;
getline( i, tmp ); // size line, without last parameter getline splits by lines
parse( tmp, size ); // obtain size as string
for ( int i = 0; i < size; ++i )
{
InventoryItem data;
getline( i, tmp ); // read an inventory line
parse( tmp, data );
}
return i;
}
}
In the vector reading function I have used getline + parse to read the integer. That is to guarantee that the next getline() will actually read the first InventoryItem and not the trailing '\n' after the size.
The most important piece of code there is the 'parse' template that is able to convert from a string to any type that has the insertion operator defined. It can be used to read primitive types, library types (string, for example), and user types that have the operator defined. We use it to simplify the rest of the code quite a bit.
Binary
For a binary format, (ignoring architecture, this will be a pain in the ass if you migrate) the simplest way I can think of is writing the number of elemements in the vector as a size_t (whatever the size is in your implementation), followed by all the elements. Each element will printout the binary representation of each of its members. For basic types as int, it will just output the binary format of the int. For strings we will resort to writting a size_t number with the number of characters in the string followed by the contents of the string.
namespace binary
{
void write( std::ofstream & o, std::string const & str )
{
int size = str.size();
o.write( &size, sizeof(int) ); // write the size
o.write( str.c_str(), size ); // write the contents
}
template <typename T>
void write_pod( std::ofstream & o, T data ) // will work only with POD data and not arrays
{
o.write( &data, sizeof( data ) );
}
void write( std::ofstream & o, InventoryItem const & data )
{
write( o, data.Item );
write( o, data.Description );
write_pod( o, data.Quantity );
write_pod( o, data. ...
}
void write( std::ofstream & o, std::vector<InventoryItem> const & v )
{
int size = v.size();
o.write( &size, sizeof( size ) ); // could use the template: write_pod( o, size )
for ( int i = 0; i < v.size(); ++i ) {
write( o, v[ i ] );
}
}
}
I have selected a different name for the template that writes basic types than the functions that write strings or InventoryItems. The reason is that we don't want to later on by mistake use the template to write a complex type (i.e. UserInfo containing strings) that will store an erroneous representation in disk.
Retrieval from disk should be fairly similar:
namespace binary {
template <typename T>
void read_pod( std::istream & i, T& data)
{
i.read( &data, sizeof(data) );
}
void read( std::istream & i, std::string & str )
{
int size;
read_pod( i, size );
char* buffer = new char[size+1]; // create a temporary buffer and read into it
i.read( buffer, size );
buffer[size] = 0;
str = buffer;
delete [] buffer;
}
void read( std::istream & i, InventoryItem & data )
{
read( i, data.Item );
read( i, data.Description );
read( i, data.Quantity );
read( i, ...
}
void read( std::istream & i, std::vector< InventoryItem > & v )
{
v.clear(); // clear the vector in case it is not empty
int size;
read_pod( i, size );
for ( int i = 0; i < size; ++i )
{
InventoryItem item;
read( i, item );
v.push_back( item );
}
}
}
For using this approach, the std::istream and std::ostream must be opened in binary mode.
int main()
{
std::ifstream persisted( "file.bin", ios:in|ios::binary );
std::vector<InventoryItem> v;
binary::read( persisted, v );
// work on data
std::ofstream persist( "output.bin", ios::out|ios::binary );
binary::write( persist, v );
}
All error checking is left as an exercise for the reader :)
If you have any question on any part of the code, just ask.
EDIT: Trying to clear up FUD:
bind1st is part of STL's functional header. STL existed before boost showed up. It is deprecated in C++0x in favor of the more generic version i.e. bind (aka boost::bind). See Annex D.8 Binders for more information.
Now the real problem (multiple edits may make this look silly, but I'll keep this for posterity's sake):
write<long>(out, structList.size());
This is the offending line. This expects a long as the second parameter, whereas the vector's size() is of type size_t or unsigned int under the hoods.
Update there was a typo: use size_t and not size_T:
write<size_t>(out, structList.size());
Next part:
for_each(structList.begin(), structList.end(), bind1st(write<InventoryItem>, out));
This should be structList or some other type. Also, include functional to be able to use bind1st. Add at the top:
#include <functional>
The template bind1st takes a functor. Passing around ordinary function pointers is not possible without some other hacks. You can use boost::bind as an alternative. Or:
for(InventoryItem::iterator i = structList.begin(), f = structList.end();
i != f; ++i)
write<InventoryItem>(out, *i);
Now for other nitpicks:
What is:
#include <String>
...
using namespace System;
Are you sure of what you are using here? If you want STL strings you need to include:
#include <string>
void main(void)
is not a standard signature. Use any one of:
int main(void)
or
int main(int argc, char *argv[]);
I/O is usually much easier with the predefined insertion/extraction operators. You can (and really should) use:
istream is(...);
is >> data;
and similarly
ostream os(...);
os << data;
Note also your readFromFile and writeToFile functions need to be fixed to use vector<InventoryItem> instead of vector simply.